bunny筆記|webpack入門到高階(鞏固與復習)

webpack從入門到進階

第1章 課程介紹

學什么

本質上,webpack 是一個現(xiàn)代 JavaScript 應用程序的靜態(tài)模塊打包器(module bundler)曙咽。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關系圖(dependency graph)颁井,其中包含應用程序需要的每個模塊淫奔,然后將所有這些模塊打包成一個或多個 bundle津畸。

  • 代碼轉譯
  • 模塊合并
  • 混淆壓縮
  • 代碼分割
  • 自動刷新
  • 代碼校驗
  • 自動部署

課程安排

  • webpack基礎配置
  • webpack高級配置
  • webpack性能優(yōu)化
  • tapable鉤子
  • AST抽象語法樹的應用
  • webpack原理分析, 手寫webpack
  • 手寫常見的loader和plugin

學習前提

  • JS基礎
  • ES6 / ES7 語法
  • node基礎
  • npm的基本使用

課程目標

  • 掌握webpack的安裝
  • 掌握webpack的基礎配置
  • 掌握loader的配置
  • 掌握plugin的配置
  • 了解webpack性能優(yōu)化
  • 了解webpack中的tapable
  • 了解AST的應用
  • 深入學習webpack原理振定,手寫webpack

第2章 webpack基礎

webpack的安裝

注意:請先自行安裝nodejs最新版的環(huán)境

  • 全局安裝webpack

    npm i webpack webpack-cli -g

  • 項目中安裝webpack (推薦)

    npm i webpack webpack-cli -D

webpack的使用

webpack-cli

npm 5.2 以上的版本中提供了一個npx命令

npx 想要解決的主要問題,就是調(diào)用項目內(nèi)部安裝的模塊肉拓,原理就是在node_modules下的.bin 目錄中找到對應的命令執(zhí)行

使用webpack命令:npx webpack

webpack4.0之后可以實現(xiàn)0配置打包構建吩案,0配置的特點就是限制較多,無法自定義很多配置

開發(fā)中常用的還是使用webpack配置進行打包構建

webpack配置

webpack有四大核心概念:

  • 入口(entry): 程序的入口js
  • 輸出(output): 打包后存放的位置
  • loader: 用于對模塊的源代碼進行轉換
  • 插件(plugins): 插件目的在于解決 loader無法實現(xiàn)的其他事
  1. 配置webpack.config.js
  2. 運行npx webpack
const path = require('path')

module.exports = {
  // 入口文件配置
  entry: './src/index.js',
  // 出口文件配置項
  output: {
    // 輸出的路徑帝簇,webpack2起就規(guī)定必須是絕對路徑
    path: path.join(__dirname, 'dist'),
    // 輸出文件名字
    filename: 'bundle.js'
  },
  mode: 'development' // 默認為production, 可以手動設置為development, 區(qū)別就是是否進行壓縮混淆
}

npx webpack命令配置到package.json的腳本中

  1. 配置package.json
  2. 運行npm run build
{
  "name": "webpack-basic",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.1"
  }
}

開發(fā)時自動編譯工具

每次要編譯代碼時徘郭,手動運行 npm run build 就會變得很麻煩。

webpack 中有幾個不同的選項丧肴,可以幫助你在代碼發(fā)生變化后自動編譯代碼:

  1. webpack's Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware

多數(shù)場景中残揉,可能需要使用 webpack-dev-server,但是不妨探討一下以上的所有選項芋浮。

watch

webpack指令后面加上--watch參數(shù)即可

主要的作用就是監(jiān)視本地項目文件的變化, 發(fā)現(xiàn)有修改的代碼會自動編譯打包, 生成輸出文件

  1. 配置package.json的scripts"watch": "webpack --watch"

  2. 運行npm run watch

以上是cli的方式設置watch的參數(shù)

還可以通過配置文件對watch的參數(shù)進行修改:

const path = require('path')

// webpack的配置文件遵循著CommonJS規(guī)范
module.exports = {
  entry: './src/main.js',
  output: {
    // path.resolve() : 解析當前相對路徑的絕對路徑
    // path: path.resolve('./dist/'),
    // path: path.resolve(__dirname, './dist/'),
    path: path.join(__dirname, './dist/'),
    filename: 'bundle.js'
  },
  mode: 'development',
  watch: true
}

運行npm run build

webpack-dev-server (推薦)

  1. 安裝devServer

    devServer需要依賴webpack抱环,必須在項目依賴中安裝webpack

    npm i webpack-dev-server webpack -D

  2. index.html中修改 <script src="/bundle.js"></script>

  3. 運行:npx webpack-dev-server

  4. 運行:npx webpack-dev-server --hot --open --port 8090

  5. 配置package.json的scripts:"dev": "webpack-dev-server --hot --open --port 8090"

  6. 運行npm run dev

devServer會在內(nèi)存中生成一個打包好的bundle.js壳快,專供開發(fā)時使用,打包效率高镇草,修改代碼后會自動重新打包以及刷新瀏覽器眶痰,用戶體驗非常好

以上是cli的方式設置devServer的參數(shù)

還可以通過配置文件對devServer的參數(shù)進行修改:

  1. 修改webpack.config.js
const path = require('path')

module.exports = {
  // 入口文件配置
  entry: './src/index.js',
  // 出口文件配置項
  output: {
    // 輸出的路徑,webpack2起就規(guī)定必須是絕對路徑
    path: path.join(__dirname, 'dist'),
    // 輸出文件名字
    filename: 'bundle.js'
  },
  devServer: {
    port: 8090,
    open: true,
    hot: true
  },
  mode: 'development'
}
  1. 修改package.json的scripts: "dev": "webpack-dev-server"
  2. 運行npm run dev

html插件

  1. 安裝html-webpack-plugin插件npm i html-webpack-plugin -D
  2. webpack.config.js中的plugins節(jié)點下配置
const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
    new HtmlWebpackPlugin({
        filename: 'index.html',
        template: 'template.html'
    })
]
  1. devServer時根據(jù)模板在express項目根目錄下生成html文件(類似于devServer生成內(nèi)存中的bundle.js)
  2. devServer時自動引入bundle.js
  3. 打包時會自動生成index.html

webpack-dev-middleware

webpack-dev-middleware 是一個容器(wrapper)梯啤,它可以把 webpack 處理后的文件傳遞給一個服務器(server)竖伯。 webpack-dev-server 在內(nèi)部使用了它,同時因宇,它也可以作為一個單獨的包來使用七婴,以便進行更多自定義設置來實現(xiàn)更多的需求。

  1. 安裝 expresswebpack-dev-middleware

    npm i express webpack-dev-middleware -D

  2. 新建server.js

    const express = require('express');
    const webpack = require('webpack');
    const webpackDevMiddleware = require('webpack-dev-middleware');
    const config = require('./webpack.config.js');
    
    const app = express();
    const compiler = webpack(config);
    
    app.use(webpackDevMiddleware(compiler, {
      publicPath: '/'
    }));
    
    app.listen(3000, function () {
      console.log('http://localhost:3000');
    });
    
  3. 配置package.json中的scripts:"server": "node server.js"

  4. 運行: npm run server

注意: 如果要使用webpack-dev-middleware, 必須使用html-webpack-plugin插件, 否則html文件無法正確的輸出到express服務器的根目錄

小結

只有在開發(fā)時才需要使用自動編譯工具, 例如: webpack-dev-server

項目上線時都會直接使用webpack進行打包構建, 不需要使用這些自動編譯工具

自動編譯工具只是為了提高開發(fā)體驗

處理css

  1. 安裝npm i css-loader style-loader -D
  2. 配置webpack.config.js
  module: {
    rules: [
      // 配置的是用來解析.css文件的loader(style-loader和css-loader)
      {
        // 用正則匹配當前訪問的文件的后綴名是  .css
        test: /\.css$/,
        use: ['style-loader', 'css-loader'] // webpack底層調(diào)用這些包的順序是從右到左
      }
    ]
  }

loader的釋義:

  1. css-loader: 解析css文件
  2. style-loader: 將解析出來的結果 放到html中, 使其生效

處理less 和 sass

npm i less less-loader sass-loader node-sass -D

{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }

處理圖片和字體

  1. npm i file-loader url-loader -D

url-loader封裝了file-loader, 所以使用url-loader時需要安裝file-loader

{
    test: /\.(png|jpg|gif)/,
    use: [{
        loader: 'url-loader',
        options: {
            // limit表示如果圖片大于5KB察滑,就以路徑形式展示打厘,小于的話就用base64格式展示
            limit: 5 * 1024,
            // 打包輸出目錄
            outputPath: 'images',
            // 打包輸出圖片名稱
            name: '[name]-[hash:4].[ext]'
        }
    }]
}

babel

  1. npm i babel-loader @babel/core @babel/preset-env webpack -D

  2. 如果需要支持更高級的ES6語法, 可以繼續(xù)安裝插件:

    npm i @babel/plugin-proposal-class-properties -D

    也可以根據(jù)需要在babel官網(wǎng)找插件進行安裝

{
  test: /\.js$/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: ['@babel/env'],
      plugins: ['@babel/plugin-proposal-class-properties']
    }
  },
  exclude: /node_modules/
}

官方更建議的做法是在項目根目錄下新建一個.babelrc的babel配置文件

{
  "presets": ["@babel/env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

如果需要使用generator,無法直接使用babel進行轉換贺辰,因為會將generator轉換為一個regeneratorRuntime户盯,然后使用markwrap來實現(xiàn)generator

但由于babel并沒有內(nèi)置regeneratorRuntime,所以無法直接使用

需要安裝插件:

`npm i @babel/plugin-transform-runtime -D`

同時還需安裝運行時依賴:

`npm i @babel/runtime -D`

.babelrc中添加插件:

{
  "presets": [
    "@babel/env"
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-runtime"
  ]
}

如果需要使用ES6/7中對象原型提供的新方法饲化,babel默認情況無法轉換先舷,即使用了transform-runtime的插件也不支持轉換原型上的方法

需要使用另一個模塊:

`npm i @babel/polyfill -S`

該模塊需要在使用新方法的地方直接引入:

`import '@babel/polyfill'`

source map的使用

devtool

此選項控制是否生成,以及如何生成 source map滓侍。

使用 SourceMapDevToolPlugin 進行更細粒度的配置。查看 source-map-loader 來處理已有的 source map牲芋。

選擇一種 source map 格式來增強調(diào)試過程撩笆。不同的值會明顯影響到構建(build)和重新構建(rebuild)的速度。

可以直接使用 SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin 來替代使用 devtool 選項缸浦,它有更多的選項夕冲,但是切勿同時使用 devtool 選項和 SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin 插件。因為devtool 選項在內(nèi)部添加過這些插件裂逐,所以會應用兩次插件歹鱼。

devtool 構建速度 重新構建速度 生產(chǎn)環(huán)境 品質(quality)
(none) +++ +++ yes 打包后的代碼
eval +++ +++ no 生成后的代碼
cheap-eval-source-map + ++ no 轉換過的代碼(僅限行)
cheap-module-eval-source-map o ++ no 原始源代碼(僅限行)
eval-source-map -- + no 原始源代碼
cheap-source-map + o no 轉換過的代碼(僅限行)
cheap-module-source-map o - no 原始源代碼(僅限行)
inline-cheap-source-map + o no 轉換過的代碼(僅限行)
inline-cheap-module-source-map o - no 原始源代碼(僅限行)
source-map -- -- yes 原始源代碼
inline-source-map -- -- no 原始源代碼
hidden-source-map -- -- yes 原始源代碼
nosources-source-map -- -- yes 無源代碼內(nèi)容

這么多模式用哪個好?

開發(fā)環(huán)境推薦:

**cheap-module-eval-source-map**

生產(chǎn)環(huán)境推薦:

**none(不使用source map)**

原因如下:

  1. 使用 cheap 模式可以大幅提高 soure map 生成的效率卜高。大部分情況我們調(diào)試并不關心列信息弥姻,而且就算 source map 沒有列,有些瀏覽器引擎(例如 v8) 也會給出列信息掺涛。
  2. 使用 module 可支持 babel 這種預編譯工具庭敦,映射轉換前的代碼
  3. 使用 eval 方式可大幅提高持續(xù)構建效率薪缆。官方文檔提供的速度對比表格可以看到 eval 模式的重新構建速度都很快秧廉。
  4. 使用 eval-source-map 模式可以減少網(wǎng)絡請求。這種模式開啟 DataUrl 本身包含完整 sourcemap 信息,并不需要像 sourceURL 那樣疼电,瀏覽器需要發(fā)送一個完整請求去獲取 sourcemap 文件嚼锄,這會略微提高點效率。而生產(chǎn)環(huán)境中則不宜用 eval蔽豺,這樣會讓文件變得極大区丑。

插件

clean-webpack-plugin

該插件在npm run build時自動清除dist目錄后重新生成,非常方便

  1. 安裝插件

    npm i clean-webpack-plugin -D

  2. 引入插件

    const CleanWebpackPlugin = require('clean-webpack-plugin')
    
  3. 使用插件, 在plugins中直接創(chuàng)建對象即可

    plugins: [
        new HtmlWebpackPlugin({
          filename: 'index.html',
          template: './src/index.html'
        }),
        new CleanWebpackPlugin()
      ],
    

copy-webpack-plugin

  1. 安裝插件

    npm i copy-webpack-plugin -D

  2. 引入插件

    const CopyWebpackPlugin = require('copy-webpack-plugin')
    
  3. 使用插件, 在plugins中插件對象并配置源和目標

    from: 源, 從哪里拷貝, 可以是相對路徑或絕對路徑, 推薦絕對路徑

    to: 目標, 拷貝到哪里去, 相對于output的路徑, 同樣可以相對路徑或絕對路徑, 但更推薦相對路徑(直接算相對dist目錄即可)

    plugins: [
        new HtmlWebpackPlugin({
          filename: 'index.html',
          template: './src/index.html'
        }),
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin([
          {
            from: path.join(__dirname, 'assets'),
            to: 'assets'
          }
        ])
      ],
    

BannerPlugin

這是一個webpack的內(nèi)置插件茫虽,用于給打包的JS文件加上版權注釋信息

  1. 引入webpack

    const webpack = require('webpack')
    
  2. 創(chuàng)建插件對象

    plugins: [
        new HtmlWebpackPlugin({
          filename: 'index.html',
          template: './src/index.html'
        }),
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin([
          {
            from: path.join(__dirname, 'assets'),
            to: 'assets'
          }
        ]),
        new webpack.BannerPlugin('黑馬程序員牛逼!')
      ],
    

第3章 webpack高級配置

HTML中img標簽的圖片資源處理

  1. 安裝npm install -S html-withimg-loader

  2. webpack.config.js文件中添加loader

    {
        test: /\.(htm|html)$/i,
        loader: 'html-withimg-loader'
    }
    

    使用時刊苍,只需要在html中正常引用圖片即可,webpack會找到對應的資源進行打包濒析,并修改html中的引用路徑

多頁應用打包

  1. webpack.config.js中修改入口和出口配置

      // 1. 修改為多入口
      entry: {
          main: './src/main.js',
          other: './src/other.js'
      },
      output: {
        path: path.join(__dirname, './dist/'),
        // filename: 'bundle.js',
        // 2. 多入口無法對應一個固定的出口, 所以修改filename為[name]變量
        filename: '[name].js',
        publicPath: '/'
      },
      plugins: [
          // 3. 如果用了html插件,需要手動配置多入口對應的html文件,將指定其對應的輸出文件
          new HtmlWebpackPlugin({
              template: './index.html',
              filename: 'index.html',
              chunks: ['main']
          }),
          new HtmlWebpackPlugin({
              template: './index.html',
              filename: 'other.html',
              // chunks: ['other', 'main']
              chunks: ['other']
          })
      ]
    
  2. 修改入口為對象正什,支持多個js入口,同時修改output輸出的文件名為'[name].js'表示各自已入口文件名作為輸出文件名号杏,但是html-webpack-plugin不支持此功能婴氮,所以需要再拷貝一份插件,用于生成兩個html頁面盾致,實現(xiàn)多頁應用

第三方庫的兩種引入方式

可以通過expose-loader進行全局變量的注入主经,同時也可以使用內(nèi)置插件webpack.ProvidePlugin對每個模塊的閉包空間,注入一個變量庭惜,自動加載模塊罩驻,而不必到處 importrequire

  • expose-loader 將庫引入到全局作用域

    1. 安裝expose-loader

      npm i -D expose-loader

    2. 配置loader

      module: {
        rules: [{
          test: require.resolve('jquery'),
          use: {
            loader: 'expose-loader',
            options: '$'
          }
        }]
      }
      

      tips: require.resolve 用來獲取模塊的絕對路徑。所以這里的loader只會作用于 jquery 模塊护赊。并且只在 bundle 中使用到它時惠遏,才進行處理。

  • webpack.ProvidePlugin 將庫自動加載到每個模塊

    1. 引入webpack

      const webpack = require('webpack')
      
    2. 創(chuàng)建插件對象

      要自動加載 jquery骏啰,我們可以將兩個變量都指向對應的 node 模塊

      new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery'
      })
      

Development / Production不同配置文件打包

項目開發(fā)時一般需要使用兩套配置文件节吮,用于開發(fā)階段打包(不壓縮代碼,不優(yōu)化代碼判耕,增加效率)和上線階段打包(壓縮代碼透绩,優(yōu)化代碼,打包后直接上線使用)

抽取三個配置文件:

  • webpack.base.js

  • webpack.prod.js

  • webpack.dev.js

步驟如下:

  1. 將開發(fā)環(huán)境和生產(chǎn)環(huán)境公用的配置放入base中壁熄,不同的配置各自放入prod或dev文件中(例如:mode)

  2. 然后在dev和prod中使用webpack-merge把自己的配置與base的配置進行合并后導出

    npm i -D webpack-merge

  3. 將package.json中的腳本參數(shù)進行修改帚豪,通過--config手動指定特定的配置文件

定義環(huán)境變量

除了區(qū)分不同的配置文件進行打包,還需要在開發(fā)時知道當前的環(huán)境是開發(fā)階段或上線階段草丧,所以可以借助內(nèi)置插件DefinePlugin來定義環(huán)境變量志鞍。最終可以實現(xiàn)開發(fā)階段與上線階段的api地址自動切換。

  1. 引入webpack

    const webpack = require('webpack')
    
  2. 創(chuàng)建插件對象方仿,并定義環(huán)境變量

    new webpack.DefinePlugin({
      IS_DEV: 'false'
    })
    
  3. 在src打包的代碼環(huán)境下可以直接使用

使用devServer解決跨域問題

在開發(fā)階段很多時候需要使用到跨域固棚,何為跨域统翩?(度娘)

開發(fā)階段往往會遇到上面這種情況,也許將來上線后此洲,前端項目會和后端項目部署在同一個服務器下厂汗,并不會有跨域問題,但是由于開發(fā)時會用到webpack-dev-server呜师,所以一定會產(chǎn)生跨域的問題

目前解決跨域主要的方案有:

  1. jsonp(淘汰)
  2. cors
  3. http proxy

此處介紹的使用devServer解決跨域娶桦,其實原理就是http proxy

將所有ajax請求發(fā)送給devServer服務器,再由devServer服務器做一次轉發(fā)汁汗,發(fā)送給數(shù)據(jù)接口服務器

由于ajax請求是發(fā)送給devServer服務器的衷畦,所以不存在跨域,而devServer由于是用node平臺發(fā)送的http請求知牌,自然也不涉及到跨域問題祈争,可以完美解決!

服務器代碼(返回一段字符串即可):

const express = require('express')
const app = express()
// const cors = require('cors')
// app.use(cors())
app.get('/api/getUserInfo', (req, res) => {
  res.send({
    name: '黑馬兒',
    age: 13
  })
});

app.listen(9999, () => {
  console.log('http://localhost:9999!');
});

前端需要配置devServer的proxy功能角寸,在webpack.dev.js中進行配置:

devServer: {
    open: true,
    hot: true,
    compress: true,
    port: 3000,
    // contentBase: './src'
    proxy: {
      '/api': 'http://localhost:9999'
    }
  },

意為前端請求/api的url時菩混,webpack-dev-server會將請求轉發(fā)給http://localhost:9999/api處,此時如果請求地址為http://localhost:9999/api/getUserInfo扁藕,只需要直接寫/api/getUserInfo即可沮峡,代碼如下:

axios.get('/api/getUserInfo').then(result => console.log(result))

HMR的使用

需要對某個模塊進行熱更新時,可以通過module.hot.accept方法進行文件監(jiān)視

只要模塊內(nèi)容發(fā)生變化亿柑,就會觸發(fā)回調(diào)函數(shù)邢疙,從而可以重新讀取模塊內(nèi)容,做對應的操作

if (module.hot) {
  module.hot.accept('./hotmodule.js', function() {
    console.log('hotmodule.js更新了');
    let str = require('./hotmodule.js')
    console.log(str)
  })
}

第4章 webpack優(yōu)化

production模式打包自帶優(yōu)化

  • tree shaking

    tree shaking 是一個術語望薄,通常用于打包時移除 JavaScript 中的未引用的代碼(dead-code)疟游,它依賴于 ES6 模塊系統(tǒng)中 importexport靜態(tài)結構特性。

    開發(fā)時引入一個模塊后式矫,如果只使用其中一個功能,上線打包時只會把用到的功能打包進bundle役耕,其他沒用到的功能都不會打包進來采转,可以實現(xiàn)最基礎的優(yōu)化

  • scope hoisting

    scope hoisting的作用是將模塊之間的關系進行結果推測, 可以讓 Webpack 打包出來的代碼文件更小瞬痘、運行的更快

    scope hoisting 的實現(xiàn)原理其實很簡單:分析出模塊之間的依賴關系故慈,盡可能的把打散的模塊合并到一個函數(shù)中去,但前提是不能造成代碼冗余框全。
    因此只有那些被引用了一次的模塊才能被合并察绷。

    由于 scope hoisting 需要分析出模塊之間的依賴關系,因此源碼必須采用 ES6 模塊化語句津辩,不然它將無法生效拆撼。
    原因和tree shaking一樣容劳。

  • 代碼壓縮

    所有代碼使用UglifyJsPlugin插件進行壓縮、混淆

css優(yōu)化

將css提取到獨立的文件中

mini-css-extract-plugin是用于將CSS提取為獨立的文件的插件闸度,對每個包含css的js文件都會創(chuàng)建一個CSS文件竭贩,支持按需加載css和sourceMap

只能用在webpack4中,有如下優(yōu)勢:

  • 異步加載
  • 不重復編譯莺禁,性能很好
  • 容易使用
  • 只針對CSS

使用方法:

  1. 安裝

    npm i -D mini-css-extract-plugin

  2. 在webpack配置文件中引入插件

    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    
  3. 創(chuàng)建插件對象留量,配置抽離的css文件名,支持placeholder語法

    new MiniCssExtractPlugin({
     filename: '[name].css'
    })
    
  4. 將原來配置的所有style-loader替換為MiniCssExtractPlugin.loader

    {
    test: /\.css$/,
    // webpack讀取loader時 是從右到左的讀取, 會將css文件先交給最右側的loader來處理
    

// loader的執(zhí)行順序是從右到左以管道的方式鏈式調(diào)用
// css-loader: 解析css文件
// style-loader: 將解析出來的結果 放到html中, 使其生效
// use: ['style-loader', 'css-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},
// { test: /.less/, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\.less/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
// { test: /.s(a|c)ss/, use: ['style-loader', 'css-loader', 'sass-loader'] }, { test: /\.s(a|c)ss/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },



### 自動添加css前綴

使用`postcss`哟冬,需要用到`postcss-loader`和`autoprefixer`插件

1. 安裝

`npm i -D postcss-loader autoprefixer`

2. 修改webpack配置文件中的loader楼熄,將`postcss-loader`放置在`css-loader`的右邊(調(diào)用鏈從右到左)

```js
{
test: /\.css$/,
// webpack讀取loader時 是從右到左的讀取, 會將css文件先交給最右側的loader來處理
// loader的執(zhí)行順序是從右到左以管道的方式鏈式調(diào)用
// css-loader: 解析css文件
// style-loader: 將解析出來的結果 放到html中, 使其生效
// use: ['style-loader', 'css-loader']
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},
// { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
{ test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] },
// { test: /\.s(a|c)ss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.s(a|c)ss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'] },
  1. 項目根目錄下添加postcss的配置文件:postcss.config.js

  2. postcss的配置文件中使用插件

    module.exports = {
      plugins: [require('autoprefixer')]
    }
    

開啟css壓縮

需要使用optimize-css-assets-webpack-plugin插件來完成css壓縮

但是由于配置css壓縮時會覆蓋掉webpack默認的優(yōu)化配置,導致JS代碼無法壓縮浩峡,所以還需要手動把JS代碼壓縮插件導入進來:terser-webpack-plugin

  1. 安裝

    npm i -D optimize-css-assets-webpack-plugin terser-webpack-plugin

  2. 導入插件

    const TerserJSPlugin = require('terser-webpack-plugin')
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
    
  3. 在webpack配置文件中添加配置節(jié)點

    optimization: {
      minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
    },
    

tips: webpack4默認采用的JS壓縮插件為:uglifyjs-webpack-plugin可岂,在mini-css-extract-plugin上一個版本中還推薦使用該插件,但最新的v0.6中建議使用teser-webpack-plugin來完成js代碼壓縮红符,具體原因未在官網(wǎng)說明青柄,我們就按照最新版的官方文檔來做即可

js代碼分離

Code Splitting是webpack打包時用到的重要的優(yōu)化特性之一,此特性能夠把代碼分離到不同的 bundle 中预侯,然后可以按需加載或并行加載這些文件致开。代碼分離可以用于獲取更小的 bundle,以及控制資源加載優(yōu)先級萎馅,如果使用合理双戳,會極大影響加載時間。

有三種常用的代碼分離方法:

  • 入口起點(entry points):使用entry配置手動地分離代碼糜芳。
  • 防止重復(prevent duplication):使用 SplitChunksPlugin去重和分離 chunk飒货。
  • 動態(tài)導入(dynamic imports):通過模塊的內(nèi)聯(lián)函數(shù)調(diào)用來分離代碼。

手動配置多入口

  1. 在webpack配置文件中配置多個入口

    entry: {
      main: './src/main.js',
      other: './src/other.js'
    },
    output: {
      // path.resolve() : 解析當前相對路徑的絕對路徑
      // path: path.resolve('./dist/'),
      // path: path.resolve(__dirname, './dist/'),
      path: path.join(__dirname, '..', './dist/'),
      // filename: 'bundle.js',
      filename: '[name].bundle.js',
      publicPath: '/'
    },
    
  2. 在main.js和other.js中都引入同一個模塊峭竣,并使用其功能

    main.js

    import $ from 'jquery'
    
    $(function() {
      $('<div></div>').html('main').appendTo('body')
    })
    

    other.js

    import $ from 'jquery'
    
    $(function() {
      $('<div></div>').html('other').appendTo('body')
    })
    
  3. 修改package.json的腳本塘辅,添加一個使用dev配置文件進行打包的腳本(目的是不壓縮代碼檢查打包的bundle時更方便)

    "scripts": {
        "build": "webpack --config ./build/webpack.prod.js",
        "dev-build": "webpack --config ./build/webpack.dev.js"
    }
    
  4. 運行npm run dev-build,進行打包

  5. 查看打包后的結果皆撩,發(fā)現(xiàn)other.bundle.js和main.bundle.js都同時打包了jQuery源文件

這種方法存在一些問題:

  • 如果入口 chunks 之間包含重復的模塊扣墩,那些重復模塊都會被引入到各個 bundle 中。
  • 這種方法不夠靈活扛吞,并且不能將核心應用程序邏輯進行動態(tài)拆分代碼呻惕。

抽取公共代碼

tips: Webpack v4以上使用的插件為SplitChunksPlugin,以前使用的CommonsChunkPlugin已經(jīng)被移除了滥比,最新版的webpack只需要在配置文件中的optimization節(jié)點下添加一個splitChunks屬性即可進行相關配置

  1. 修改webpack配置文件

    optimization: {
        splitChunks: {
          chunks: 'all'
        }
    },
    
  2. 運行npm run dev-build重新打包

  3. 查看dist目錄

  4. 查看vendors~main~other.bundle.js亚脆,其實就是把都用到的jQuery打包到了一個單獨的js中

動態(tài)導入 (懶加載)

webpack4默認是允許import語法動態(tài)導入的,但是需要babel的插件支持盲泛,最新版babel的插件包為:@babel/plugin-syntax-dynamic-import濒持,以前老版本不是@babel開頭键耕,已經(jīng)無法使用,需要注意

動態(tài)導入最大的好處是實現(xiàn)了懶加載弥喉,用到哪個模塊才會加載哪個模塊郁竟,可以提高SPA應用程序的首屏加載速度,Vue由境、React棚亩、Angular框架的路由懶加載原理一樣

  1. 安裝babel插件

    npm install -D @babel/plugin-syntax-dynamic-import

  2. 修改.babelrc配置文件,添加@babel/plugin-syntax-dynamic-import插件

    {
      "presets": ["@babel/env"],
      "plugins": [
        "@babel/plugin-proposal-class-properties",
        "@babel/plugin-transform-runtime",
        "@babel/plugin-syntax-dynamic-import"
      ]
    }
    
  3. 將jQuery模塊進行動態(tài)導入

    function getComponent() {
      return import('jquery').then(({ default: $ }) => {
        return $('<div></div>').html('main')
      })
    }
    
  4. 給某個按鈕添加點擊事件虏杰,點擊后調(diào)用getComponent函數(shù)創(chuàng)建元素并添加到頁面

    window.onload = function () {
      document.getElementById('btn').onclick = function () {
        getComponent().then(item => {
          item.appendTo('body')
        })
      }
    }
    

SplitChunksPlugin配置參數(shù)

webpack4之后讥蟆,使用SplitChunksPlugin插件替代了以前CommonsChunkPlugin

SplitChunksPlugin的配置,只需要在webpack配置文件中的optimization節(jié)點下的splitChunks進行修改即可纺阔,如果沒有任何修改瘸彤,則會使用默認配置

默認的SplitChunksPlugin 配置適用于絕大多數(shù)用戶

webpack 會基于如下默認原則自動分割代碼:

  • 公用代碼塊或來自 node_modules 文件夾的組件模塊。
  • 打包的代碼塊大小超過 30k(最小化壓縮之前)笛钝。
  • 按需加載代碼塊時质况,同時發(fā)送的請求最大數(shù)量不應該超過 5。
  • 頁面初始化時玻靡,同時發(fā)送的請求最大數(shù)量不應該超過 3结榄。

以下是SplitChunksPlugin的默認配置:

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', // 只對異步加載的模塊進行拆分,可選值還有all | initial
      minSize: 30000, // 模塊最少大于30KB才拆分
      maxSize: 0,  // 模塊大小無上限囤捻,只要大于30KB都拆分
      minChunks: 1, // 模塊最少引用一次才會被拆分
      maxAsyncRequests: 5, // 異步加載時同時發(fā)送的請求數(shù)量最大不能超過5,超過5的部分不拆分
      maxInitialRequests: 3, // 頁面初始化時同時發(fā)送的請求數(shù)量最大不能超過3,超過3的部分不拆分
      automaticNameDelimiter: '~', // 默認的連接符
      name: true, // 拆分的chunk名,設為true表示根據(jù)模塊名和CacheGroup的key來自動生成,使用上面連接符連接
      cacheGroups: { // 緩存組配置,上面配置讀取完成后進行拆分,如果需要把多個模塊拆分到一個文件,就需要緩存,所以命名為緩存組
        vendors: { // 自定義緩存組名
          test: /[\\/]node_modules[\\/]/, // 檢查node_modules目錄,只要模塊在該目錄下就使用上面配置拆分到這個組
          priority: -10 // 權重-10,決定了哪個組優(yōu)先匹配,例如node_modules下有個模塊要拆分,同時滿足vendors和default組,此時就會分到vendors組,因為-10 > -20
        },
        default: { // 默認緩存組名
          minChunks: 2, // 最少引用兩次才會被拆分
          priority: -20, // 權重-20
          reuseExistingChunk: true // 如果主入口中引入了兩個模塊,其中一個正好也引用了后一個,就會直接復用,無需引用兩次
        }
      }
    }
  }
};

noParse

在引入一些第三方模塊時臼朗,例如jQuery、bootstrap等蝎土,我們知道其內(nèi)部肯定不會依賴其他模塊视哑,因為最終我們用到的只是一個單獨的js文件或css文件

所以此時如果webpack再去解析他們的內(nèi)部依賴關系,其實是非常浪費時間的誊涯,我們需要阻止webpack浪費精力去解析這些明知道沒有依賴的庫

可以在webpack配置文件的module節(jié)點下加上noParse挡毅,并配置正則來確定不需要解析依賴關系的模塊

module: {
    noParse: /jquery|bootstrap/
}

IgnorePlugin

在引入一些第三方模塊時,例如moment暴构,內(nèi)部會做i18n國際化處理跪呈,所以會包含很多語言包,而語言包打包時會比較占用空間丹壕,如果我們項目只需要用到中文庆械,或者少數(shù)語言薇溃,可以忽略掉所有的語言包菌赖,然后按需引入語言包

從而使得構建效率更高,打包生成的文件更小

需要忽略第三方模塊內(nèi)部依賴的其他模塊沐序,只需要三步:

  1. 首先要找到moment依賴的語言包是什么
  2. 使用IgnorePlugin插件忽略其依賴
  3. 需要使用某些依賴時自行手動引入

具體實現(xiàn)如下:

  1. 通過查看moment的源碼來分析:

    function loadLocale(name) {
        var oldLocale = null;
        // TODO: Find a better way to register and load all the locales in Node
        if (!locales[name] && (typeof module !== 'undefined') &&
            module && module.exports) {
            try {
                oldLocale = globalLocale._abbr;
                var aliasedRequire = require;
                aliasedRequire('./locale/' + name);
                getSetGlobalLocale(oldLocale);
            } catch (e) {}
        }
        return locales[name];
    }
    
    

    觀察上方代碼琉用,同時查看moment目錄下確實有l(wèi)ocale目錄堕绩,其中放著所有國家的語言包,可以分析得出:locale目錄就是moment所依賴的語言包目錄

  2. 使用IgnorePlugin插件來忽略掉moment模塊的locale目錄

    在webpack配置文件中安裝插件邑时,并傳入配置項

    參數(shù)1:表示要忽略的資源路徑

    參數(shù)2:要忽略的資源上下文(所在哪個目錄)

    兩個參數(shù)都是正則對象

    new webpack.IgnorePlugin(/\.\/locale/, /moment/)
    
  3. 使用moment時需要手動引入語言包奴紧,否則默認使用英文

    import moment from 'moment'
    import 'moment/locale/zh-cn'
    moment.locale('zh-CN')
    console.log(moment().subtract(6, 'days').calendar())
    

DllPlugin

在引入一些第三方模塊時,例如vue晶丘、react黍氮、angular等框架,這些框架的文件一般都是不會修改的浅浮,而每次打包都需要去解析它們沫浆,也會影響打包速度,哪怕做拆分滚秩,也只是提高了上線后用戶訪問速度专执,并不會提高構建速度,所以如果需要提高構建速度郁油,應該使用動態(tài)鏈接庫的方式本股,類似于Windows中的dll文件。

借助DllPlugin插件實現(xiàn)將這些框架作為一個個的動態(tài)鏈接庫桐腌,只構建一次拄显,以后每次構建都只生成自己的業(yè)務代碼,可以大大提高構建效率哩掺!

主要思想在于凿叠,將一些不做修改的依賴文件,提前打包嚼吞,這樣我們開發(fā)代碼發(fā)布的時候就不需要再對這部分代碼進行打包盒件,從而節(jié)省了打包時間。

涉及兩個插件:

  1. DllPlugin

    使用一個單獨webpack配置創(chuàng)建一個dll文件舱禽。并且它還創(chuàng)建一個manifest.json炒刁。DllReferencePlugin使用該json文件來做映射依賴性。(這個文件會告訴我們的哪些文件已經(jīng)提取打包好了)

    配置參數(shù):

    • context (可選): manifest文件中請求的上下文誊稚,默認為該webpack文件上下文翔始。
    • name: 公開的dll函數(shù)的名稱,和output.library保持一致即可里伯。
    • path: manifest.json生成的文件夾及名字
  2. DllReferencePlugin

    這個插件用于主webpack配置城瞎,它引用的dll需要預先構建的依賴關系。

    • context: manifest文件中請求的上下文疾瓮。

    • manifest: DllPlugin插件生成的manifest.json

    • content(可選): 請求的映射模塊id(默認為manifest.content)

    • name(可選): dll暴露的名稱

    • scope(可選): 前綴用于訪問dll的內(nèi)容

    • sourceType(可選): dll是如何暴露(libraryTarget)

將Vue項目中的庫抽取成Dll

  1. 準備一份將Vue打包成DLL的webpack配置文件

    在build目錄下新建一個文件:webpack.vue.js

    配置入口:將多個要做成dll的庫全放進來

    配置出口:一定要設置library屬性脖镀,將打包好的結果暴露在全局

    配置plugin:設置打包后dll文件名和manifest文件所在地

    const path = require('path')
    const webpack = require('webpack')
    module.exports = {
      mode: 'development',
      entry: {
        vue: [
          'vue/dist/vue.js',
          'vue-router'
        ]
      },
      output: {
        filename: '[name]_dll.js',
        path: path.resolve(__dirname, '../dist'),
        library: '[name]_dll'
      },
      plugins: [
        new webpack.DllPlugin({
          name: '[name]_dll',
          path: path.resolve(__dirname, '../dist/manifest.json')
        })
      ]
    }
    
  2. 在webpack.base.js中進行插件的配置

    使用DLLReferencePlugin指定manifest文件的位置即可

    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dist/manifest.json')
    })
    
  3. 安裝add-asset-html-webpack-plugin

    npm i add-asset-html-webpack-plugin -D

  4. 配置插件自動添加script標簽到HTML中

    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dist/vue_dll.js')
    })
    

將React項目中的庫抽取成Dll

  1. 準備一份將React打包成DLL的webpack配置文件

    在build目錄下新建一個文件:webpack.vue.js

    配置入口:將多個要做成dll的庫全放進來

    配置出口:一定要設置library屬性,將打包好的結果暴露在全局

    配置plugin:設置打包后dll文件名和manifest文件所在地

    const path = require('path')
    const webpack = require('webpack')
    module.exports = {
      mode: 'development',
      entry: {
        react: [
          'react',
          'react-dom'
        ]
      },
      output: {
        filename: '[name]_dll.js',
        path: path.resolve(__dirname, '../dist'),
        library: '[name]_dll'
      },
      plugins: [
        new webpack.DllPlugin({
          name: '[name]_dll',
          path: path.resolve(__dirname, '../dist/manifest.json')
        })
      ]
    }
    
  2. 在webpack.base.js中進行插件的配置

    使用DLLReferencePlugin指定manifest文件的位置即可

    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dist/manifest.json')
    })
    
  3. 安裝add-asset-html-webpack-plugin

    npm i add-asset-html-webpack-plugin -D

  4. 配置插件自動添加script標簽到HTML中

    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dist/react_dll.js')
    })
    

Happypack

由于webpack在node環(huán)境中運行打包構建狼电,所以是單線程的模式蜒灰,在打包眾多資源時效率會比較低下弦蹂,早期可以通過Happypack來實現(xiàn)多進程打包。當然强窖,這個問題只出現(xiàn)在低版本的webpack中凸椿,現(xiàn)在的webpack性能已經(jīng)非常強勁了,所以無需使用Happypack也可以實現(xiàn)高性能打包

Happypack官網(wǎng)

引用官網(wǎng)原文:

Maintenance mode notice

My interest in the project is fading away mainly because I'm not using JavaScript as much as I was in the past. Additionally, Webpack's native performance is improving and (I hope) it will soon make this plugin unnecessary.

See the FAQ entry about Webpack 4 and thread-loader.

Contributions are always welcome. Changes I make from this point will be restricted to bug-fixing. If someone wants to take over, feel free to get in touch.

Thanks to everyone who used the library, contributed to it and helped in refining it!!!

由此可以看出作者已經(jīng)發(fā)現(xiàn)翅溺,webpack的性能已經(jīng)強大到不需要使用該插件了脑漫,而且小項目使用該插件反而會導致性能損耗過大,因為開啟進程是需要耗時的

使用方法:

  1. 安裝插件

    npm i -D happypack

  2. 在webpack配置文件中引入插件

    const HappyPack = require('happypack')
    
  3. 修改loader的配置規(guī)則

    {
      test: /.js$/,
      use: {
          loader: 'happypack/loader'
        },
      include: path.resolve(__dirname, '../src'),
      exclude: /node_modules/
    }
    
  4. 配置插件

    new HappyPack({
        loaders: [ 'babel-loader' ]
    })
    
  5. 運行打包命令

    npm run build

瀏覽器緩存

在做了眾多代碼分離的優(yōu)化后咙崎,其目的是為了利用瀏覽器緩存窿撬,達到提高訪問速度的效果,所以構建項目時做代碼分割是必須的叙凡,例如將固定的第三方模塊抽離劈伴,下次修改了業(yè)務代碼,重新發(fā)布上線不重啟服務器握爷,用戶再次訪問服務器就不需要再次加載第三方模塊了

但此時會遇到一個新的問題跛璧,如果再次打包上線不重啟服務器,客戶端會把以前的業(yè)務代碼和第三方模塊同時緩存新啼,再次訪問時依舊會訪問緩存中的業(yè)務代碼追城,所以會導致業(yè)務代碼也無法更新

需要在output節(jié)點的filename中使用placeholder語法,根據(jù)代碼內(nèi)容生成文件名的hash:

output: {
    // path.resolve() : 解析當前相對路徑的絕對路徑
    // path: path.resolve('./dist/'),
    // path: path.resolve(__dirname, './dist/'),
    path: path.join(__dirname, '..', './dist/'),
    // filename: 'bundle.js',
    filename: '[name].[contenthash:8].bundle.js',
    publicPath: '/'
  },

之后每次打包業(yè)務代碼時燥撞,如果有改變座柱,會生成新的hash作為文件名,瀏覽器就不會使用緩存了物舒,而第三方模塊不會重新打包生成新的名字色洞,則會繼續(xù)使用緩存

打包分析

項目構建完成后,需要通過一些工具對打包后的bundle進行分析冠胯,通過分析才能總結出一些經(jīng)驗火诸,官方推薦的分析方法有兩步完成:

  1. 使用--profile --json參數(shù),以json格式來輸出打包后的結果到某個指定文件中

    webpack --profile --json > stats.json

  2. 將stats.json文件放入工具中進行分析

    官方工具:official analyze tool

    官方推薦的其他四個工具:

    其中webpack-bundle-analyzer是一個插件荠察,可以以插件的方式安裝到項目中

Prefetching和Preloading

在優(yōu)化訪問性能時置蜀,除了充分利用瀏覽器緩存之外,還需要涉及一個性能指標:coverage rate(覆蓋率)

可以在Chrome瀏覽器的控制臺中按:ctrl + shift + p悉盆,查找coverage盯荤,打開覆蓋率面板

開始錄制后刷新網(wǎng)頁,即可看到每個js文件的覆蓋率焕盟,以及總的覆蓋率

想提高覆蓋率滤奈,需要盡可能多的使用動態(tài)導入棚瘟,也就是懶加載功能,將一切能使用懶加載的地方都是用懶加載,這樣可以大大提高覆蓋率

但有時候使用懶加載會影響用戶體驗瑰谜,所以可以在懶加載時使用魔法注釋:Prefetching,是指在首頁資源加載完畢后兔簇,空閑時間時塘装,將動態(tài)導入的資源加載進來,這樣即可以提高首屏加載速度备图,也可以解決懶加載可能會影響用戶體驗的問題灿巧,一舉兩得!

function getComponent() {
  return import(/* webpackPrefetch: true */ 'jquery').then(({ default: $ }) => {
    return $('<div></div>').html('我是main')
  })
}

(備注說明:學習筆記源-大地)

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揽涮,一起剝皮案震驚了整個濱河市抠藕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒋困,老刑警劉巖盾似,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異雪标,居然都是意外死亡零院,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門村刨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來告抄,“玉大人,你說我怎么就攤上這事嵌牺〈蛲荩” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵逆粹,是天一觀的道長募疮。 經(jīng)常有香客問我,道長僻弹,這世上最難降的妖魔是什么酝锅? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮奢方,結果婚禮上搔扁,老公的妹妹穿的比我還像新娘。我一直安慰自己蟋字,他們只是感情好稿蹲,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鹊奖,像睡著了一般苛聘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天设哗,我揣著相機與錄音唱捣,去河邊找鬼。 笑死网梢,一個胖子當著我的面吹牛震缭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播战虏,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拣宰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烦感?” 一聲冷哼從身側響起巡社,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎手趣,沒想到半個月后晌该,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡绿渣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年气笙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怯晕。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡潜圃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舟茶,到底是詐尸還是另有隱情谭期,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布吧凉,位于F島的核電站隧出,受9級特大地震影響,放射性物質發(fā)生泄漏阀捅。R本人自食惡果不足惜胀瞪,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饲鄙。 院中可真熱鬧凄诞,春花似錦、人聲如沸忍级。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轴咱。三九已至汛蝙,卻和暖如春烈涮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窖剑。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工坚洽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人西土。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓讶舰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親翠储。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354