webpack最全實(shí)用配置(收藏)

1. 入門(一起來(lái)用這些小例子讓你熟悉webpack的配置)

1.1 初始化項(xiàng)目

新建一個(gè)目錄,初始化npm

npm init

webpack是運(yùn)行在node環(huán)境中的,我們需要安裝以下兩個(gè)npm包

npm i -D webpack webpack-cli
  • npm i -D 為npm install --save-dev的縮寫
  • npm i -S 為npm install --save的縮寫

新建一個(gè)文件夾src ,然后新建一個(gè)文件main.js,寫一點(diǎn)代碼測(cè)試一下

console.log('call me 老yuan')

配置package.json命令

59f09701704cb2a02f14f738f13bd7b2.png

執(zhí)行

npm run build

此時(shí)如果生成了一個(gè)dist文件夾矫钓,并且內(nèi)部含有main.js說(shuō)明已經(jīng)打包成功了

1.2 開始我們自己的配置

上面一個(gè)簡(jiǎn)單的例子只是webpack自己默認(rèn)的配置毡琉,下面我們要實(shí)現(xiàn)更加豐富的自定義配置

新建一個(gè)build文件夾,里面新建一個(gè)webpack.config.js

// webpack.config.js
const path = require('path');
module.exports = {
    mode: 'development', // 開發(fā)模式   
    entry: path.resolve(__dirname, '../src/main.js'),
    // 入口文件  
    output: {
        filename: 'output.js', // 打包后的文件名稱  
        path: path.resolve(__dirname, '../dist') // 打包后的目錄
    }
}

更改我們的打包命令

fe33a8e067a27311098fb56c59325eb6.png

執(zhí)行 npm run build
會(huì)發(fā)現(xiàn)生成了以下目錄(圖片)
其中dist文件夾中的main.js就是我們需要在瀏覽器中實(shí)際運(yùn)行的文件
當(dāng)然實(shí)際運(yùn)用中不會(huì)僅僅如此,下面讓我們通過(guò)實(shí)際案例帶你快速入手webpack

1.3 配置html模板

js文件打包好了,但是我們不可能每次在html文件中手動(dòng)引入打包好的js

這里可能有的朋友會(huì)認(rèn)為我們打包js文件名稱不是一直是固定的嘛( output.js)铁瞒?這樣每次就不用改動(dòng)引入文件名稱了呀?實(shí)際上我們?nèi)粘i_發(fā)中往往會(huì)這樣配置:

module.exports = { // 省略其他配置   
    output: {
        filename: '[name].[hash:8].js', // 打包后的文件名稱  
        path: path.resolve(__dirname, '../dist') // 打包后的目錄
    }
}

這時(shí)候生成的dist目錄文件如下

40f2f617f29192c8903cc107f7433d08.png

為了緩存桅滋,你會(huì)發(fā)現(xiàn)打包好的js文件的名稱每次都不一樣慧耍。webpack打包出來(lái)的js文件我們需要引入到html中,但是每次我們都手動(dòng)修改js文件名顯得很麻煩丐谋,因此我們需要一個(gè)插件來(lái)幫我們完成這件事情

npm i -D html-webpack-plugin

新建一個(gè)build同級(jí)的文件夾public,里面新建一個(gè)index.html
具體配置文件如下

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = {
    mode: 'development', // 開發(fā)模式   
    entry: path.resolve(__dirname, '../src/main.js'), // 入口文件  
    output: {
        filename: '[name].[hash:8].js', // 打包后的文件名稱   
        path: path.resolve(__dirname, '../dist') // 打包后的目錄  
    },
    plugins: [new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../public/index.html')
    })]
}

生成目錄如下(圖片)

6da20cc5c3fda1c096fca0a2abb0a5a0.png

可以發(fā)現(xiàn)打包生成的js文件已經(jīng)被自動(dòng)引入html文件中

1.3.1. 多入口文件如何開發(fā)

生成多個(gè) html-webpack-plugin實(shí)例來(lái)解決這個(gè)問(wèn)題

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    mode: 'development', // 開發(fā)模式   
    entry: {
        main: path.resolve(__dirname, '../src/main.js'),
        header: path.resolve(__dirname, '../src/header.js')
    },
    output: {
        filename: '[name].[hash:8].js', // 打包后的文件名稱  
        path: path.resolve(__dirname, '../dist') // 打包后的目錄 
    },
    plugins: [new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../public/index.html'),
        filename: 'index.html',
        chunks: ['main'] // 與入口文件對(duì)應(yīng)的模塊名    
    }), new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../public/header.html'),
        filename: 'header.html',
        chunks: ['header'] // 與入口文件對(duì)應(yīng)的模塊名  
    }), ]
}

此時(shí)會(huì)發(fā)現(xiàn)生成以下目錄

bc1ca7c754aedeeac89b038a5a3969bb.png

1.3.2 clean-webpack-plugin

每次執(zhí)行npm run build 會(huì)發(fā)現(xiàn)dist文件夾里會(huì)殘留上次打包的文件芍碧,這里我們推薦一個(gè)plugin來(lái)幫我們?cè)诖虬敵銮扒蹇瘴募A clean-webpack-plugin

const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {    // ...省略其他配置    
plugins:[new CleanWebpackPlugin()]
}

1.4 引用CSS

我們的入口文件是js,所以我們?cè)谌肟趈s中引入我們的css文件

28caa7b6a2f26aabda40b239c0354350.png

同時(shí)我們也需要一些loader來(lái)解析我們的css文件

npm i -D style-loader css-loader

如果我們使用less來(lái)構(gòu)建樣式号俐,則需要多安裝兩個(gè)

npm i -D less less-loader

配置文件如下

// webpack.config.js
module.exports = { // ...省略其他配置  
    module: {
        rules: [{
            test: /.css$/,
            use: ['style-loader', 'css-loader'] // 從右向左解析原則    
        }, {
            test: /.less$/,
            use: ['style-loader', 'css-loader', 'less-loader'] // 從右向左解析原則      
        }]
    }
}

瀏覽器打開html如下

5fdfdd2d6de878b325326e523d412346.png

1.4.1 為css添加瀏覽器前綴

npm i -D postcss-loader autoprefixer

配置如下

// webpack.config.js
module.exports = {
    module: {
        rules: [test / .less$ / , use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'] // 從右向左解析原則  
        ]
    }
}

接下來(lái)师枣,我們還需要引入autoprefixer使其生效,這里有兩種方式

1,在項(xiàng)目根目錄下創(chuàng)建一個(gè)postcss.config.js文件萧落,配置如下:

module.exports = {    plugins: [require('autoprefixer')]  // 引用該插件即可了}

2践美,直接在webpack.config.js里配置

// webpack.config.js
module.exports = { //...省略其他配置  
    module: {
        rules: [{
            test: /.less$/,
            use: ['style-loader', 'css-loader', {
                loader: 'postcss-loader',
                options: {
                    plugins: [require('autoprefixer')]
                }
            }, 'less-loader'] // 從右向左解析原則 
        }]
    }
}

這時(shí)候我們發(fā)現(xiàn)css通過(guò)style標(biāo)簽的方式添加到了html文件中,但是如果樣式文件很多找岖,全部添加到html中陨倡,難免顯得混亂。這時(shí)候我們想用把css拆分出來(lái)用外鏈的形式引入css文件怎么做呢许布?這時(shí)候我們就需要借助插件來(lái)幫助我們

1.4.2 拆分css

npm i -D mini-css-extract-plugin

webpack 4.0以前兴革,我們通過(guò) extract-text-webpack-plugin插件,把css樣式從js文件中提取到單獨(dú)的css文件中蜜唾。webpack4.0以后杂曲,官方推薦使用 mini-css-extract-plugin插件來(lái)打包c(diǎn)ss文件

配置文件如下

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = { //...省略其他配置  
    module: {
        rules: [{
            test: /.less$/,
            use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
        }]
    },
    plugins: [new MiniCssExtractPlugin({
        filename: "[name].[hash].css",
        chunkFilename: "[id].css",
    })]
}

1.4.3 拆分多個(gè)css

這里需要說(shuō)的細(xì)一點(diǎn),上面我們所用到的 mini-css-extract-plugin會(huì)將所有的css樣式合并為一個(gè)css文件。如果你想拆分為一一對(duì)應(yīng)的多個(gè)css文件,我們需要使用到 extract-text-webpack-plugin袁余,而目前 mini-css-extract-plugin還不支持此功能擎勘。我們需要安裝@next版本的 extract-text-webpack-plugin

npm i - D extract - text - webpack - plugin @next // webpack.config.js
const path = require('path');
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
let indexLess = new ExtractTextWebpackPlugin('index.less');
let indexCss = new ExtractTextWebpackPlugin('index.css');
module.exports = {
    module: {
        rules: [{
            test: /.css$/,
            use: indexCss.extract({
                use: ['css-loader']
            })
        }, {
            test: /.less$/,
            use: indexLess.extract({
                use: ['css-loader', 'less-loader']
            })
        }]
    },
    plugins: [indexLess, indexCss]
}

1.5 打包 圖片、字體颖榜、媒體棚饵、等文件

file-loader就是將文件在進(jìn)行一些處理后(主要是處理文件名和路徑、解析文件url)掩完,并將文件移動(dòng)到輸出的目錄中url-loader 一般與file-loader搭配使用噪漾,功能與 file-loader 類似,如果文件小于限制的大小且蓬。則會(huì)返回 base64 編碼欣硼,否則使用 file-loader 將文件移動(dòng)到輸出的目錄中

// webpack.config.js
module.exports = { // 省略其它配置 ...  
    module: {
        rules: [ // ...     
            {
                test: /.(jpe?g|png|gif)$/i, //圖片文件 
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10240,
                        fallback: {
                            loader: 'file-loader',
                            options: {
                                name: 'img/[name].[hash:8].[ext]'
                            }
                        }
                    }
                }]
            }, {
                test: /.(mp4|webm|ogg|mp3|wav|flac|aac)(?.*)?$/, //媒體文件 
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10240,
                        fallback: {
                            loader: 'file-loader',
                            options: {
                                name: 'media/[name].[hash:8].[ext]'
                            }
                        }
                    }
                }]
            }, {
                test: /.(woff2?|eot|ttf|otf)(?.*)?$/i, // 字體
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10240,
                        fallback: {
                            loader: 'file-loader',
                            options: {
                                name: 'fonts/[name].[hash:8].[ext]'
                            }
                        }
                    }
                }]
            },
        ]
    }
}

1.6 用babel轉(zhuǎn)義js文件

為了使我們的js代碼兼容更多的環(huán)境我們需要安裝依賴

npm i babel-loader @babel/preset-env @babel/core
  • 注意

babel-loaderbabel-core的版本對(duì)應(yīng)關(guān)系

  1. babel-loader 8.x 對(duì)應(yīng)babel-core 7.x
  2. babel-loader 7.x 對(duì)應(yīng)babel-core 6.x

配置如下

// webpack.config.js
module.exports = { // 省略其它配置 ... 
    module: {
        rules: [{
            test: /.js$/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env']
                }
            },
            exclude: /node_modules/
        }, ]
    }
}

上面的babel-loader只會(huì)將 ES6/7/8語(yǔ)法轉(zhuǎn)換為ES5語(yǔ)法,但是對(duì)新api并不會(huì)轉(zhuǎn)換 例如(promise恶阴、Generator诈胜、Set、Maps存淫、Proxy等)
此時(shí)我們需要借助babel-polyfill來(lái)幫助我們轉(zhuǎn)換

npm i @babel/polyfill
// webpack.config.js
const path = require('path')module.exports = {  
  entry: ["@babel/polyfill,path.resolve(__dirname,'../src/index.js')"],    // 入口文件}
  • 手動(dòng)把上面的demo敲一遍對(duì)閱讀下面的文章更有益耘斩,建議入門的同學(xué)敲三遍以上

上面的實(shí)踐是我們對(duì)webpack的功能有了一個(gè)初步的了解,但是要想熟練應(yīng)用于開發(fā)中桅咆,我們需要一個(gè)系統(tǒng)的實(shí)戰(zhàn)括授。讓我們一起擺脫腳手架嘗試自己搭建一個(gè)vue開發(fā)環(huán)境

2. 搭建vue開發(fā)環(huán)境

上面的小例子已經(jīng)幫助而我們實(shí)現(xiàn)了打包c(diǎn)ss、圖片岩饼、js荚虚、html等文件。
但是我們還需要以下幾種配置

2.1 解析.vue文件

npm i -D vue-loader vue-template-compiler vue-style-loadernpm i -S vue

vue-loader 用于解析.vue文件vue-template-compiler 用于編譯模板
配置如下

const vueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
    module: {
        rules: [{
            test: /.vue$/,
            use: ['vue-loader']
        }, ]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.runtime.esm.js',
            ' @': path.resolve(__dirname, '../src')
        },
        extensions: ['*', '.js', '.json', '.vue']
    },
    plugins: [new vueLoaderPlugin()]
}

2.2 配置webpack-dev-server進(jìn)行熱更新

npm i -D webpack-dev-server

配置如下

const Webpack = require('webpack')
module.exports = { // ...省略其他配置 
    devServer: {
        port: 3000,
        hot: true,
        contentBase: '../dist'
    },
    plugins: [new Webpack.HotModuleReplacementPlugin()]
}

完整配置如下

// webpack.config.js
const path = require('path');
const {
    CleanWebpackPlugin
} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const vueLoaderPlugin = require('vue-loader/lib/plugin')
const Webpack = require('webpack')
module.exports = {
    mode: 'development', // 開發(fā)模式   
    entry: {
        main: path.resolve(__dirname, '../src/main.js'),
    },
    output: {
        filename: '[name].[hash:8].js', // 打包后的文件名稱 
        path: path.resolve(__dirname, '../dist') // 打包后的目錄 
    },
    module: {
        rules: [{
            test: /.vue$/,
            use: ['vue-loader']
        }, {
            test: /.js$/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: [
                        ['@babel/preset-env']
                    ]
                }
            }
        }, {
            test: /.css$/,
            use: ['vue-style-loader', 'css-loader', {
                loader: 'postcss-loader',
                options: {
                    plugins: [require('autoprefixer')]
                }
            }]
        }, {
            test: /.less$/,
            use: ['vue-style-loader', 'css-loader', {
                loader: 'postcss-loader',
                options: {
                    plugins: [require('autoprefixer')]
                }
            }, 'less-loader']
        }]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.runtime.esm.js',
            ' @': path.resolve(__dirname, '../src')
        },
        extensions: ['*', '.js', '.json', '.vue']
    },
    devServer: {
        port: 3000,
        hot: true,
        contentBase: '../dist'
    },
    plugins: [new CleanWebpackPlugin(), new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../public/index.html'),
        filename: 'index.html'
    }), new vueLoaderPlugin(), new Webpack.HotModuleReplacementPlugin()]
}

2.3 配置打包命令

83e3eb20ea09be8d47edd1ab631e2cb3.png

打包文件已經(jīng)配置完畢籍茧,接下來(lái)讓我們測(cè)試一下
首先在src新建一個(gè)main.js

34b9342d1bd1b24e90dfecd45c4eb090.png

新建一個(gè)App.vue

421deeabdfc47804d536a62274ee7a8c.png

新建一個(gè)public文件夾版述,里面新建一個(gè)index.html

44f6ea02073654e737e34f3f7676dcd9.png

執(zhí)行npm run dev這時(shí)候如果瀏覽器出現(xiàn)Vue開發(fā)環(huán)境運(yùn)行成功,那么恭喜你寞冯,已經(jīng)成功邁出了第一步

2.4 區(qū)分開發(fā)環(huán)境與生產(chǎn)環(huán)境

實(shí)際應(yīng)用到項(xiàng)目中渴析,我們需要區(qū)分開發(fā)環(huán)境與生產(chǎn)環(huán)境晚伙,我們?cè)谠瓉?lái)webpack.config.js的基礎(chǔ)上再新增兩個(gè)文件

  • webpack.dev.js 開發(fā)環(huán)境配置文件
開發(fā)環(huán)境主要實(shí)現(xiàn)的是熱更新,不要壓縮代碼,完整的sourceMap
  • webpack.prod.js生產(chǎn)環(huán)境配置文件
生產(chǎn)環(huán)境主要實(shí)現(xiàn)的是壓縮代碼俭茧、提取css文件咆疗、合理的sourceMap、分割代碼需要安裝以下模塊:npm i -D  webpack-merge copy-webpack-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin
  • webpack-merge 合并配置
  • copy-webpack-plugin 拷貝靜態(tài)資源
  • optimize-css-assets-webpack-plugin 壓縮css
  • uglifyjs-webpack-plugin 壓縮js

webpack mode設(shè)置 production的時(shí)候會(huì)自動(dòng)壓縮js代碼母债。原則上不需要引入 uglifyjs-webpack-plugin進(jìn)行重復(fù)工作午磁。但是 optimize-css-assets-webpack-plugin壓縮css的同時(shí)會(huì)破壞原有的js壓縮,所以這里我們引入 uglifyjs進(jìn)行壓縮

2.4.1 webpack.config.js

const path = require('path')
const {
    CleanWebpackPlugin
} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const vueLoaderPlugin = require('vue-loader/lib/plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const devMode = process.argv.indexOf('--mode=production') === -1;
module.exports = {
    entry: {
        main: path.resolve(__dirname, '../src/main.js')
    },
    output: {
        path: path.resolve(__dirname, '../dist'),

        filename: 'js/[name].[hash:8].js',
        chunkFilename: 'js/[name].[hash:8].js'
    },
    module: {
        rules: [{
            test: /.js$/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env']
                }
            },
            exclude: /node_modules/
        }, {
            test: /.vue$/,
            use: ['cache-loader', 'thread-loader', {
                loader: 'vue-loader',
                options: {
                    compilerOptions: {
                        preserveWhitespace: false
                    }
                }
            }]
        }, {
            test: /.css$/,
            use: [{
                loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
                options: {
                    publicPath: "../dist/css/",
                    hmr: devMode
                }
            }, 'css-loader', {
                loader: 'postcss-loader',
                options: {
                    plugins: [require('autoprefixer')]
                }
            }]
        }, {
            test: /.less$/,
            use: [{
                loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
                options: {
                    publicPath: "../dist/css/",
                    hmr: devMode
                }
            }, 'css-loader', 'less-loader', {
                loader: 'postcss-loader',
                options: {
                    plugins: [require('autoprefixer')]
                }
            }]
        }, {
            test: /.(jep?g|png|gif)$/,
            use: {
                loader: 'url-loader',
                options: {
                    limit: 10240,
                    fallback: {
                        loader: 'file-loader',
                        options: {
                            name: 'img/[name].[hash:8].[ext]'
                        }
                    }
                }
            }
        }, {
            test: /.(mp4|webm|ogg|mp3|wav|flac|aac)(?.*)?$/,
            use: {
                loader: 'url-loader',
                options: {
                    limit: 10240,
                    fallback: {
                        loader: 'file-loader',
                        options: {
                            name: 'media/[name].[hash:8].[ext]'
                        }
                    }
                }
            }
        }, {
            test: /.(woff2?|eot|ttf|otf)(?.*)?$/i,
            use: {
                loader: 'url-loader',
                options: {
                    limit: 10240,
                    fallback: {
                        loader: 'file-loader',
                        options: {
                            name: 'media/[name].[hash:8].[ext]'
                        }
                    }
                }
            }
        }]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.runtime.esm.js',
            ' @': path.resolve(__dirname, '../src')
        },
        extensions: ['*', '.js', '.json', '.vue']
    },
    plugins: [new CleanWebpackPlugin(), new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../public/index.html')
    }), new vueLoaderPlugin(), new MiniCssExtractPlugin({
        filename: devMode ? '[name].css' : '[name].[hash].css',
        chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
    })]
}

2.4.2 webpack.dev.js

const Webpack = require('webpack')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
module.exports = WebpackMerge(webpackConfig, {
    mode: 'development',
    devtool: 'cheap-module-eval-source-map',
    devServer: {
        port: 3000,
        hot: true,
        contentBase: '../dist'
    },
    plugins: [new Webpack.HotModuleReplacementPlugin()]
})

2.4.3 webpack.prod.js

const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = WebpackMerge(webpackConfig, {
    mode: 'production',
    devtool: 'cheap-module-source-map',
    plugins: [
        new CopyWebpackPlugin([{
            from: path.resolve(__dirname, '../public'),
            to: path.resolve(__dirname, '../dist')
        }]),
    ],
    optimization: {
        minimizer: [new UglifyJsPlugin({ //壓縮js 
            cache: true,
            parallel: true,
            sourceMap: true
        }), new OptimizeCssAssetsPlugin({})],
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                libs: {
                    name: "chunk-libs",
                    test: /[/]node_modules[/]/,
                    priority: 10,
                    chunks: "initial" // 只打包初始時(shí)依賴的第三方   
                }
            }
        }
    }
})

2.5 優(yōu)化webpack配置

看到這里你或許有些累了毡们,但是要想獲取更好的offer,更高的薪水迅皇,下面必須繼續(xù)深入

2f2be2cd559266f3bed5d93d2d902510.png

優(yōu)化配置對(duì)我們來(lái)說(shuō)非常有實(shí)際意義汹族,這實(shí)際關(guān)系到你打包出來(lái)文件的大小籍铁,打包的速度等江兢。
具體優(yōu)化可以分為以下幾點(diǎn):

2.5.1 優(yōu)化打包速度

構(gòu)建速度指的是我們每次修改代碼后熱更新的速度以及發(fā)布前打包文件的速度照激。

2.5.1.1 合理的配置mode參數(shù)與devtool參數(shù)

mode可設(shè)置development production兩個(gè)參數(shù)

如果沒(méi)有設(shè)置耗帕,webpack4 會(huì)將 mode 的默認(rèn)值設(shè)置為 production``production模式下會(huì)進(jìn)行tree shaking(去除無(wú)用代碼)和uglifyjs(代碼壓縮混淆)

2.5.1.2 縮小文件的搜索范圍(配置include exclude alias noParse extensions)

  • alias: 當(dāng)我們代碼中出現(xiàn) import 'vue'時(shí)扣囊, webpack會(huì)采用向上遞歸搜索的方式去node_modules 目錄下找吉嫩。為了減少搜索范圍我們可以直接告訴webpack去哪個(gè)路徑下查找其掂。也就是別名(alias)的配置脖隶。
  • include exclude 同樣配置include exclude也可以減少webpack loader的搜索轉(zhuǎn)換時(shí)間扁耐。
  • noParse 當(dāng)我們代碼中使用到import jq from 'jquery'時(shí),webpack會(huì)去解析jq這個(gè)庫(kù)是否有依賴其他的包产阱。但是我們對(duì)類似jquery這類依賴庫(kù)婉称,一般會(huì)認(rèn)為不會(huì)引用其他的包(特殊除外,自行判斷)。增加noParse屬性,告訴webpack不必解析构蹬,以此增加打包速度王暗。
  • extensions webpack會(huì)根據(jù)extensions定義的后綴查找文件(頻率較高的文件類型優(yōu)先寫在前面)
87bf48305eb3e59f5c08bfcc53fe301a.png

2.5.1.3 使用HappyPack開啟多進(jìn)程Loader轉(zhuǎn)換

在webpack構(gòu)建過(guò)程中,實(shí)際上耗費(fèi)時(shí)間大多數(shù)用在loader解析轉(zhuǎn)換以及代碼的壓縮中庄敛。日常開發(fā)中我們需要使用Loader對(duì)js俗壹,css,圖片藻烤,字體等文件做轉(zhuǎn)換操作绷雏,并且轉(zhuǎn)換的文件數(shù)據(jù)量也是非常大。由于js單線程的特性使得這些轉(zhuǎn)換操作不能并發(fā)處理文件怖亭,而是需要一個(gè)個(gè)文件進(jìn)行處理涎显。HappyPack的基本原理是將這部分任務(wù)分解到多個(gè)子進(jìn)程中去并行處理,子進(jìn)程處理完成后把結(jié)果發(fā)送到主進(jìn)程中兴猩,從而減少總的構(gòu)建時(shí)間

npm i -D happypack
a74b7e241211c4e1508ce477547be910.png

2.5.1.4 使用webpack-parallel-uglify-plugin 增強(qiáng)代碼壓縮

上面對(duì)于loader轉(zhuǎn)換已經(jīng)做優(yōu)化期吓,那么下面還有另一個(gè)難點(diǎn)就是優(yōu)化代碼的壓縮時(shí)間。

npm i -D webpack-parallel-uglify-plugin
54a7fed62d3a52b299e9850f2c40ec0d.png

2.5.1.5 抽離第三方模塊

對(duì)于開發(fā)項(xiàng)目中不經(jīng)常會(huì)變更的靜態(tài)依賴文件倾芝。類似于我們的 elementUi讨勤、vue全家桶等等箭跳。因?yàn)楹苌贂?huì)變更,所以我們不希望這些依賴要被集成到每一次的構(gòu)建邏輯中去悬襟。 這樣做的好處是每次更改我本地代碼的文件的時(shí)候衅码, webpack只需要打包我項(xiàng)目本身的文件代碼,而不會(huì)再去編譯第三方庫(kù)脊岳。以后只要我們不升級(jí)第三方包的時(shí)候,那么 webpack就不會(huì)對(duì)這些庫(kù)去打包垛玻,這樣可以快速的提高打包的速度割捅。

這里我們使用webpack內(nèi)置的DllPlugin DllReferencePlugin進(jìn)行抽離
在與webpack配置文件同級(jí)目錄下新建webpack.dll.config.js
代碼如下

// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = { // 你想要打包的模塊的數(shù)組  
    entry: {
        vendor: ['vue', 'element-ui']
    },
    output: {
        path: path.resolve(__dirname, 'static/js'), // 打包后文件輸出的位置 
        filename: '[name].dll.js',
        library: '[name]_library'
        // 這里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。 
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.resolve(__dirname, '[name]-manifest.json'),
            name: '[name]_library',
            context: __dirname
        })
    ]
};

package.json中配置如下命令

"dll": "webpack --config build/webpack.dll.config.js"

接下來(lái)在我們的webpack.config.js中增加以下代碼

module.exports = {
        plugins: [
                new webpack.DllReferencePlugin({
                    context: __dirname,
                    manifest: require('./vendor-manifest.json')
                }),
                new CopyWebpackPlugin([ // 拷貝生成的文件到dist目錄 這樣每次不必手動(dòng)去cv      {from: 'static', to:'static'}    ]),
                    ]
                };

執(zhí)行

npm run dll

會(huì)發(fā)現(xiàn)生成了我們需要的集合第三地方
代碼的vendor.dll.js
我們需要在html文件中手動(dòng)引入這個(gè)js文件

<!DOCTYPE html>
<html lang="en">
<head>  
<meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>老yuan</title>
  <script src="static/js/vendor.dll.js"></script>
</head>
<body> 
 <div id="app"></div>
</body>
</html>

這樣如果我們沒(méi)有更新第三方依賴包帚桩,就不必npm run dll亿驾。直接執(zhí)行npm run dev npm run build的時(shí)候會(huì)發(fā)現(xiàn)我們的打包速度明顯有所提升。因?yàn)槲覀円呀?jīng)通過(guò)dllPlugin將第三方依賴包抽離出來(lái)了账嚎。

2.5.1.6 配置緩存

我們每次執(zhí)行構(gòu)建都會(huì)把所有的文件都重復(fù)編譯一遍莫瞬,這樣的重復(fù)工作是否可以被緩存下來(lái)呢,答案是可以的郭蕉,目前大部分 loader 都提供了 cache 配置項(xiàng)疼邀。比如在 babel-loader 中,可以通過(guò)設(shè)置 cacheDirectory 來(lái)開啟緩存召锈, babel-loader?cacheDirectory=true 就會(huì)將每次的編譯結(jié)果寫進(jìn)硬盤文件(默認(rèn)是在項(xiàng)目根目錄下的 node_modules/.cache/babel-loader目錄內(nèi)旁振,當(dāng)然你也可以自定義)
<p>但如果 loader 不支持緩存呢?我們也有方法,我們可以通過(guò) cache-loader 涨岁,它所做的事情很簡(jiǎn)單拐袜,就是 babel-loader 開啟 cache 后做的事情,將 loader 的編譯結(jié)果寫入硬盤緩存梢薪。再次構(gòu)建會(huì)先比較一下蹬铺,如果文件較之前的沒(méi)有發(fā)生變化則會(huì)直接使用緩存。使用方法如官方 demo 所示秉撇,在一些性能開銷較大的 loader 之前添加此 loader即可</p>

npm i -D cache-loader
4ef93fe7e1d065a305d87805ccb60a73.png

2.5.2 優(yōu)化打包文件體積

打包的速度我們是進(jìn)行了優(yōu)化甜攀,但是打包后的文件體積卻是十分大,造成了頁(yè)面加載緩慢畜疾,浪費(fèi)流量等赴邻,接下來(lái)讓我們從文件體積上繼續(xù)優(yōu)化

2.5.2.1 引入webpack-bundle-analyzer分析打包后的文件

webpack-bundle-analyzer將打包后的內(nèi)容束展示為方便交互的直觀樹狀圖,讓我們知道我們所構(gòu)建包中真正引入的內(nèi)容

npm i -D webpack-bundle-analyzer
773bf92c29463e1181cb37ae3aa82d66.png

接下來(lái)在package.json里配置啟動(dòng)命令

"analyz": "NODE_ENV=production npm_config_report=true npm run build"

windows請(qǐng)安裝npm i -D cross-env

"analyz": "cross-env NODE_ENV=production npm_config_report=true npm run build"

接下來(lái)npm run analyz瀏覽器會(huì)自動(dòng)打開文件依賴圖的網(wǎng)頁(yè)

2.5.2.3 externals

按照官方文檔的解釋啡捶,如果我們想引用一個(gè)庫(kù)姥敛,但是又不想讓 webpack打包,并且又不影響我們?cè)诔绦蛑幸?CMD瞎暑、AMD或者 window/global全局等方式進(jìn)行使用彤敛,那就可以通過(guò)配置 Externals与帆。這個(gè)功能主要是用在創(chuàng)建一個(gè)庫(kù)的時(shí)候用的,但是也可以在我們項(xiàng)目開發(fā)中充分使用 Externals的方式墨榄,我們將這些不需要打包的靜態(tài)資源從構(gòu)建邏輯中剔除出去玄糟,而使用 CDN
的方式,去引用它們袄秩。
<p>有時(shí)我們希望我們通過(guò) script引入的庫(kù)阵翎,如用CDN的方式引入的 jquery,我們?cè)谑褂脮r(shí)之剧,依舊用 require的方式來(lái)使用郭卫,但是卻不希望 webpack將它又編譯進(jìn)文件中。這里官網(wǎng)案例已經(jīng)足夠清晰明了背稼,大家有興趣可以點(diǎn)擊了解 </p>

webpack
官網(wǎng)案例如下

<script  src="https://code.jquery.com/jquery-3.1.0.js"  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="  crossorigin="anonymous"></script>module.exports = { 
 //...  
externals: {    jquery: 'jQuery'  }};
import $ from 'jquery';
$('.my-element').animate(/* ... */);

2.5.2.3 Tree-shaking

這里單獨(dú)提一下 tree-shaking,是因?yàn)檫@里有個(gè)坑贰军。 tree-shaking的主要作用是用來(lái)清除代碼中無(wú)用的部分。目前在 webpack4 我們?cè)O(shè)置 modeproduction的時(shí)候已經(jīng)自動(dòng)開啟了 tree-shaking蟹肘。但是要想使其生效词疼,生成的代碼必須是ES6模塊。不能使用其它類型的模塊如 CommonJS之流帘腹。如果使用 Babel的話贰盗,這里有一個(gè)小問(wèn)題,因?yàn)?Babel的預(yù)案(preset)默認(rèn)會(huì)將任何模塊類型都轉(zhuǎn)譯成 CommonJS類型竹椒。修正這個(gè)問(wèn)題也很簡(jiǎn)單童太,在 .babelrc文件或在 webpack.config.js文件中設(shè)置 modules: false就好了

// .babelrc{  "presets": [    ["@babel/preset-env",      {        "modules": false      }    ]  ]}

或者

// webpack.config.js 
module: {
    rules: [{
        test: /.js$/,
        use: {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env', {
                    modules: false
                }]
            }
        },
        exclude: /(node_modules)/
    }]
}

經(jīng)歷過(guò)上面兩個(gè)系列的洗禮胸完,到現(xiàn)在我們成為了一名合格的webpack配置工程師书释。但是光擰螺絲,自身的可替代性還是很高赊窥,下面我們將深入webpack的原理中去

65aec257be14fb24c610d2e6cdb4bd94.png

3.手寫webpack系列

經(jīng)歷過(guò)上面兩個(gè)部分爆惧,我們已經(jīng)可以熟練的運(yùn)用相關(guān)的loader和plugin對(duì)我們的代碼進(jìn)行轉(zhuǎn)換、解析锨能。接下來(lái)我們自己手動(dòng)實(shí)現(xiàn)loader與plugin扯再,使其在平時(shí)的開發(fā)中獲得更多的樂(lè)趣。

3.1 手寫webpack loader

loader從本質(zhì)上來(lái)說(shuō)其實(shí)就是一個(gè) node模塊址遇。相當(dāng)于一臺(tái)榨汁機(jī) (loader)將相關(guān)類型的文件代碼 (code)給它熄阻。根據(jù)我們?cè)O(shè)置的規(guī)則,經(jīng)過(guò)它的一系列加工后還給我們加工好的果汁 (code)倔约。

loader編寫原則

  • 單一原則: 每個(gè) Loader 只做一件事秃殉;
  • 鏈?zhǔn)秸{(diào)用: Webpack 會(huì)按順序鏈?zhǔn)秸{(diào)用每個(gè) Loader
  • 統(tǒng)一原則: 遵循 Webpack 制定的設(shè)計(jì)規(guī)則和結(jié)構(gòu),輸入與輸出均為字符串钾军,各個(gè) Loader 完全獨(dú)立鳄袍,即插即用;

在日常開發(fā)環(huán)境中吏恭,為了方便調(diào)試我們往往會(huì)加入許多console打印拗小。但是我們不希望在生產(chǎn)環(huán)境中存在打印的值。那么這里我們自己實(shí)現(xiàn)一個(gè)loader去除代碼中的console

知識(shí)點(diǎn)普及之 AST樱哼。 AST通俗的來(lái)說(shuō)哀九,假設(shè)我們有一個(gè)文件 a.js,我們對(duì) a.js里面的1000行進(jìn)行一些操作處理,比如為所有的 await 增加 try catch,以及其他操作,但是 a.js里面的代碼本質(zhì)上來(lái)說(shuō)就是一堆字符串搅幅。那我們?cè)趺崔k呢勾栗,那就是轉(zhuǎn)換為帶標(biāo)記信息的對(duì)象(抽象語(yǔ)法樹)我們方便進(jìn)行增刪改查。這個(gè)帶標(biāo)記的對(duì)象(抽象語(yǔ)法樹)就是 AST盏筐。這里推薦一篇不錯(cuò)的AST文章 AST快速入門

npm i -D @babel/parser @babel/traverse @babel/generator @babel/types
  • @babel/parser 將源代碼解析成 AST
  • @babel/traverse 對(duì)AST節(jié)點(diǎn)進(jìn)行遞歸遍歷,生成一個(gè)便于操作砸讳、轉(zhuǎn)換的path對(duì)象
  • @babel/generatorAST解碼生成js代碼
  • @babel/types通過(guò)該模塊對(duì)具體的AST節(jié)點(diǎn)進(jìn)行進(jìn)行增琢融、刪、改簿寂、查

新建 drop-console.js

const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generator = require('@babel/generator').default
const t = require('@babel/types')
module.exports = function(source) {
    const ast = parser.parse(source, {
        sourceType: 'module'
    }) traverse(ast, {
        CallExpression(path) {
            if (t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.object, {
                    name: "console"
                })) {
                path.remove()
            }
        }
    }) const output = generator(ast, {}, source);
    return output.code
}

如何使用

const path = require('path')
module.exports = {
    mode: 'development',
    entry: path.resolve(__dirname, 'index.js'),
    output: {
        filename: '[name].[contenthash].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [{
            test: /.js$/,
            use: path.resolve(__dirname, 'drop-console.js')
        }]
    }
}

實(shí)際上在 webpack4中已經(jīng)集成了去除 console功能漾抬,在 minimizer中可配置 去除console

附上官網(wǎng) 如何編寫一個(gè)loader

3.2 手寫webpack plugin

Webpack 運(yùn)行的生命周期中會(huì)廣播出許多事件, Plugin 可以監(jiān)聽這些事件常遂,在合適的時(shí)機(jī)通過(guò) Webpack提供的 API改變輸出結(jié)果纳令。通俗來(lái)說(shuō):一盤美味的 鹽豆炒雞蛋 需要經(jīng)歷燒油 炒制 調(diào)味到最后的裝盤等過(guò)程,而 plugin相當(dāng)于可以監(jiān)控每個(gè)環(huán)節(jié)并進(jìn)行操作克胳,比如可以寫一個(gè)少放胡椒粉 plugin,監(jiān)控 webpack暴露出的生命周期事件(調(diào)味)平绩,在調(diào)味的時(shí)候執(zhí)行少放胡椒粉操作。那么它與 loader的區(qū)別是什么呢漠另?上面我們也提到了 loader的單一原則, loader只能一件事捏雌,比如說(shuō) less-loader,只能解析 less文件, plugin則是針對(duì)整個(gè)流程執(zhí)行廣泛的任務(wù)笆搓。

一個(gè)基本的plugin插件結(jié)構(gòu)如下

class firstPlugin {
    constructor(options) {
        console.log('firstPlugin options', options)
    }
    apply(compiler) {
        compiler.plugin('done', compilation => {
            console.log('firstPlugin')))
    }
}
module.exports = firstPlugin

compiler 性湿、compilation是什么?

  • compiler 對(duì)象包含了 Webpack 環(huán)境所有的的配置信息满败。這個(gè)對(duì)象在啟動(dòng) webpack 時(shí)被一次性建立肤频,并配置好所有可操作的設(shè)置,包括 options算墨,loaderplugin宵荒。當(dāng)在 webpack 環(huán)境中應(yīng)用一個(gè)插件時(shí),插件將收到此 compiler 對(duì)象的引用『龋可以使用它來(lái)訪問(wèn) webpack 的主環(huán)境摔竿。
  • compilation對(duì)象包含了當(dāng)前的模塊資源、編譯生成資源少孝、變化的文件等继低。當(dāng)運(yùn)行webpack 開發(fā)環(huán)境中間件時(shí),每當(dāng)檢測(cè)到一個(gè)文件變化稍走,就會(huì)創(chuàng)建一個(gè)新的 compilation袁翁,從而生成一組新的編譯資源。compilation 對(duì)象也提供了很多關(guān)鍵時(shí)機(jī)的回調(diào)婿脸,以供插件做自定義處理時(shí)選擇使用粱胜。

compiler和 compilation的區(qū)別在于

  • compiler代表了整個(gè)webpack從啟動(dòng)到關(guān)閉的生命周期,而compilation只是代表了一次新的編譯過(guò)程
  • compiler和compilation暴露出許多鉤子狐树,我們可以根據(jù)實(shí)際需求的場(chǎng)景進(jìn)行自定義處理

compiler鉤子文檔
compilation鉤子文檔

下面我們手動(dòng)開發(fā)一個(gè)簡(jiǎn)單的需求,在生成打包文件之前自動(dòng)生成一個(gè)關(guān)于打包出文件的大小信息

新建一個(gè)webpack-firstPlugin.js

class firstPlugin {
    constructor(options) {
        this.options = options
    }
    apply(compiler) {
        compiler.plugin('emit', (compilation, callback) => {
            let str = ''
            for (let filename in compilation.assets) {
                str += `文件:${filename}  大小${compilation.assets[filename]['size']()}n`
            } // 通過(guò)compilation.assets可以獲取打包后靜態(tài)資源信息焙压,同樣也可以寫入資源     
            compilation.assets['fileSize.md'] = {
                source: function() {
                    return str
                },
                size: function() {
                    return str.length
                }
            }
            callback()
        })
    }
}
module.exports = firstPlugin

如何使用

const path = require('path')const firstPlugin = require('webpack-firstPlugin.js')module.exports = {    // 省略其他代碼    plugins:[        new firstPlugin()    ]}

執(zhí)行 npm run build即可看到在dist文件夾中生成了一個(gè)包含打包文件信息的fileSize.md

上面兩個(gè) loaderplugin案例只是一個(gè)引導(dǎo),實(shí)際開發(fā)需求中的 loaderplugin要考慮的方面很多抑钟,建議大家自己多動(dòng)手嘗試一下涯曲。

附上官網(wǎng) 如何編寫一個(gè)plugin

3.3 手寫webpack

由于篇幅過(guò)長(zhǎng),且原理深入較多在塔。鑒于本篇以快速上手應(yīng)用于實(shí)際開發(fā)的原則幻件,這里決定另起一篇新的文章去詳細(xì)剖析 webpack原理以及實(shí)現(xiàn)一個(gè) demo版本。待格式校準(zhǔn)后蛔溃,將會(huì)貼出文章鏈接在下方

4. webpack5.0的時(shí)代

無(wú)論是前端框架還是構(gòu)建工具的更新速度遠(yuǎn)遠(yuǎn)超乎了我們的想象,前幾年的jquery一把梭的時(shí)代一去不復(fù)返绰沥。我們要擁抱的是不斷更新迭代的vue、react贺待、node徽曲、serverless、docker狠持、k8s....

v2-916338e511a20085880148dbd1d9b060_b.gif

不甘落后的webpack也已經(jīng)在近日發(fā)布了 webpack 5.0.0 beta 10 版本疟位。在之前作者也曾提過(guò)webpack5.0旨在減少配置的復(fù)雜度,使其更容易上手(webpack4的時(shí)候也說(shuō)了這句話)喘垂,以及一些性能上的提升

  • 使用持久化緩存提高構(gòu)建性能甜刻;
  • 使用更好的算法和默認(rèn)值改進(jìn)長(zhǎng)期緩存(long-term caching);
  • 清理內(nèi)部結(jié)構(gòu)而不引入任何破壞性的變化正勒;
  • 引入一些breaking changes得院,以便盡可能長(zhǎng)的使用v5版本。

目前來(lái)看章贞,維護(hù)者的更新很頻繁祥绞,相信用不了多久webpack5.0將會(huì)擁抱大眾。感興趣的同學(xué)可以先安裝beta版本嘗嘗鮮。不過(guò)在此之前建議大家先對(duì)webpack4進(jìn)行一番掌握,這樣后面的路才會(huì)越來(lái)越好走蜕径。

c95b888cee04722dffe05d5b8ea9ce19.png

原作者姓名:前端小布
原出處:segmentfault
(收藏)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末两踏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兜喻,更是在濱河造成了極大的恐慌梦染,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朴皆,死亡現(xiàn)場(chǎng)離奇詭異帕识,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)遂铡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門肮疗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人扒接,你說(shuō)我怎么就攤上這事伪货。” “怎么了钾怔?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵超歌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蒂教,道長(zhǎng),這世上最難降的妖魔是什么脆荷? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任凝垛,我火速辦了婚禮,結(jié)果婚禮上蜓谋,老公的妹妹穿的比我還像新娘梦皮。我一直安慰自己,他們只是感情好桃焕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布剑肯。 她就那樣靜靜地躺著,像睡著了一般观堂。 火紅的嫁衣襯著肌膚如雪让网。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天师痕,我揣著相機(jī)與錄音溃睹,去河邊找鬼。 笑死胰坟,一個(gè)胖子當(dāng)著我的面吹牛因篇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼竞滓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咐吼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起商佑,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锯茄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后莉御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撇吞,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年礁叔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牍颈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琅关,死狀恐怖煮岁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涣易,我是刑警寧澤画机,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布泞歉,位于F島的核電站芋簿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏俩滥。R本人自食惡果不足惜徒爹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一荚醒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧隆嗅,春花似錦界阁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至丽焊,卻和暖如春较剃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背技健。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工重付, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凫乖。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓确垫,卻偏偏與公主長(zhǎng)得像弓颈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子删掀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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