Webpack 3,從入門到放棄

原文首發(fā)于:Webpack 3洽瞬,從入門到放棄

Update (2017.8.27) : 關于 output.publicPath本涕、devServer.contentBasedevServer.publicPath的區(qū)別伙窃。如下:

  • output.publicPath: 對于這個選項菩颖,我們無需關注什么絕對相對路徑,因為兩種路徑都可以为障。我們只需要知道一點:這個選項是指定 HTML 文件中資源文件 (字體晦闰、圖片放祟、JS文件等) 的文件名的公共 URL 部分的。在實際情況中呻右,我們首先會通過output.filename或有些 loader 如file-loadername屬性設置文件名的原始部分跪妥,webpack 將文件名的原始部分和公共部分結(jié)合之后,HTML 文件就能獲取到資源文件了声滥。
  • devServer.contentBase: 設置靜態(tài)資源的根目錄眉撵,html-webpack-plugin生成的 html 不是靜態(tài)資源。當用 html 文件里的地址無法找到靜態(tài)資源文件時就會去這個目錄下去找落塑。
  • devServer.publicPath: 指定瀏覽器上訪問所有 打包(bundled)文件 (在dist里生成的所有文件) 的根目錄纽疟,這個根目錄是相對服務器地址及端口的,比devServer.contentBaseoutput.publicPath優(yōu)先憾赁。

前言

Tips
如果你用過 webpack 且一直用的是 webpack 1污朽,請參考 從v1遷移到v2 (v2 和 v3 差異不大) 對版本變更的內(nèi)容進行適當?shù)牧私猓缓笤龠x擇性地閱讀本文缠沈。

首先膘壶,這篇文章是根據(jù)當前最新的 webpack 版本 (即 v3.4.1) 撰寫,較長一段時間內(nèi)無需擔心過時的問題洲愤。其次颓芭,這應該會是一篇極長的文章,涵蓋了基本的使用方法柬赐,有更高級功能的需求可以參考官方文檔繼續(xù)學習亡问。再次,即使是基本的功能肛宋,也內(nèi)容繁多州藕,我盡可能地解釋通俗易懂,將我學習過程中的疑惑和坑一一解釋酝陈,如有紕漏床玻,敬請雅正。再次沉帮,為了清晰有效地講解锈死,我會演示從零編寫 demo,只要一步步跟著做穆壕,就會清晰許多待牵。最后,官方文檔也是個坑爹貨喇勋!

Webpack缨该,何許人也?

借用官方的說法:

webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.

簡言之川背,webpack 是一個模塊打包器 (module bundler)贰拿,能夠?qū)⑷魏钨Y源如 JavaScript 文件蛤袒、CSS 文件、圖片等打包成一個或少數(shù)文件壮不。

為什么要用介個 Webpack?

首先汗盘,定義已經(jīng)說明了 webpack 能將多個資源模塊打包成一個或少數(shù)文件皱碘,這意味著與以往的發(fā)起多個 HTTP 請求來獲得資源相比询一,現(xiàn)在只需要發(fā)起少量的 HTTP 請求。

Tips
想了解合并 HTTP 請求的意義癌椿,請見 這里健蕊。

其次,webpack 能將你的資源轉(zhuǎn)換為最適合瀏覽器的“格式”踢俄,提升應用性能缩功。比如只引用被應用使用的資源 (剔除未被使用的代碼),懶加載資源 (只在需要的時候才加載相應的資源)都办。再次嫡锌,對于開發(fā)階段,webpack 也提供了實時加載和熱加載的功能琳钉,大大地節(jié)省了開發(fā)時間势木。除此之外,還有許多優(yōu)秀之處之處值得去挖掘歌懒。不過啦桌,webpack 最核心的還是打包的功能。

webpack及皂,gulp/grunt甫男,npm,它們有什么區(qū)別?

webpack 是模塊打包器(module bundler)验烧,把所有的模塊打包成一個或少量文件板驳,使你只需加載少量文件即可運行整個應用,而無需像之前那樣加載大量的圖片碍拆,css文件若治,js文件,字體文件等等倔监。而gulp/grunt 是自動化構(gòu)建工具直砂,或者叫任務運行器(task runner),是把你所有重復的手動操作讓代碼來做浩习,例如壓縮JS代碼静暂、CSS代碼,代碼檢查谱秽、代碼編譯等等洽蛀,自動化構(gòu)建工具并不能把所有模塊打包到一起摹迷,也不能構(gòu)建不同模塊之間的依賴圖。兩者來比較的話郊供,gulp/grunt 無法做模塊打包的事峡碉,webpack 雖然有 loader 和 plugin可以做一部分 gulp/grunt 能做的事,但是終究 webpack 的插件還是不如 gulp/grunt 的插件豐富驮审,能做的事比較有限鲫寄。于是有人兩者結(jié)合著用,將 webpack 放到 gulp/grunt 中用疯淫。然而地来,更好的方法是用 npm scripts 取代 gulp/grunt,npm 是 node 的包管理器 (node package manager)熙掺,用于管理 node 的第三方軟件包未斑,npm 對于任務命令的良好支持讓你最終省卻了編寫任務代碼的必要,取而代之的币绩,是老祖宗的幾個命令行蜡秽,僅靠幾句命令行就足以完成你的模塊打包和自動化構(gòu)建的所有需求。

準備開始

先來看看一個 webpack 的一個完備的配置文件缆镣,是 介樣 的芽突,當然啦,這里面有很多配置項是即使到這個軟件被廢棄你也用不上的:)费就,所以無需擔心诉瓦。

基本配置

開始之前,請確定你已經(jīng)安裝了當前 Node 的較新版本力细。

然后執(zhí)行以下命令以新建我們的 demo 目錄:

$ mkdir webpack-demo && cd webpack-demo && npm init -y
$ npm i --save-dev webpack
$ mkdir src && cd src && touch index.js

我們使用工具函數(shù)庫 lodash 來演示我們的 demo睬澡。先安裝之:

$ npm i --save lodash

src/index.js

import _ from 'lodash';

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    
  return element;
}

document.body.appendChild(component());

Tips
importexport 已經(jīng)是 ES6 的標準,但是仍未得到大多數(shù)瀏覽器的支持 (可喜的是眠蚂, Chrome 61 已經(jīng)開始默認支持了煞聪,見 ES6 modules),不過 webpack 提供了對這個特性的支持逝慧,但是除了這個特性昔脯,其他的 ES6 特性并不會得到 webpack 的特別支持,如有需要笛臣,須借助 Babel 進行轉(zhuǎn)譯 (transpile)云稚。

然后新建發(fā)布版本目錄:

$ cd .. && mkdir dist && cd dist && touch index.html 

dist/index.html

<!DOCTYPE html>
<html>
<head>
    <title>webpack demo</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

現(xiàn)在,我們運行 webpack 來打包 index.jsbundle.js沈堡,本地安裝了 webpack 后可以通過 node_modules/.bin/webpack 來訪問 webpack 的二進制版本静陈。

$ cd ..
$ ./node_modules/.bin/webpack src/index.js dist/bundle.js # 第一個參數(shù)是打包的入口文件,第二個參數(shù)是打包的出口文件

咻咻咻,大致如下輸出一波:

Hash: de8ed072e2c7b3892179
Version: webpack 3.4.1
Time: 390ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  544 kB       0  [emitted]  [big]  main
   [0] ./src/index.js 225 bytes {0} [built]
   [2] (webpack)/buildin/global.js 509 bytes {0} [built]
   [3] (webpack)/buildin/module.js 517 bytes {0} [built]
    + 1 hidden module

現(xiàn)在鲸拥,你已經(jīng)得到了你的第一個打包文件 (bundle.js) 了矢洲。

使用配置文件

像上面這樣使用 webpack 應該是最挫的姿勢了贷掖,所以我們要使用 webpack 的配置文件來提高我們的姿勢水平聋溜。

$ touch webpack.config.js

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js', // 入口起點酥郭,可以指定多個入口起點
  output: { // 輸出,只可指定一個輸出配置
    filename: 'bundle.js', // 輸出文件名
    path: path.resolve(__dirname, 'dist') // 輸出文件所在的目錄
  }
};

執(zhí)行:

$ ./node_modules/.bin/webpack --config webpack.config.js # `--config` 制定 webpack 的配置文件撞叨,默認是 `webpack.config.js`

所以這里可以省卻 --config webpack.config.js金踪。但是每次都要寫 ./node_modules/.bin/webpack 實在讓人不爽,所以我們要動用 NPM Scripts谒所。

package.json

{
  ...
  "scripts": {
    "build": "webpack"
  },
  ...
}

Tips
npm scripts 中我們可以通過包名直接引用本地安裝的 npm 包的二進制版本热康,而無需編寫包的整個路徑沛申。

執(zhí)行:

$ npm run build

一波輸出后便得到了打包文件劣领。

Tips
bulid 并不是 npm scripts 的內(nèi)置屬性,需要使用 npm run 來執(zhí)行腳本铁材,詳情見 npm run尖淘。

打包其他類型的文件

因為其他文件和 JS 文件類型不同,要把他們加載到 JS 文件中就需要經(jīng)過加載器 (loader) 的處理著觉。

加載 CSS

我們需要安裝兩個 loader 來處理 CSS 文件:

$ npm i --save-dev style-loader css-loader

style-loader 通過插入 <style> 標簽將 CSS 加入到 DOM 中村生,css-loader 會像解釋 import/require() 一樣解釋 @import 和 url()。

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js', 
    path: path.resolve(__dirname, 'dist')
  },
  module: { // 如何處理項目中不同類型的模塊
    rules: [ // 用于規(guī)定在不同模塊被創(chuàng)建時如何處理模塊的規(guī)則數(shù)組
      {
        test: /\.css$/, // 匹配特定文件的正則表達式或正則表達式數(shù)組
        use: [ // 應用于模塊的 loader 使用列表
          'style-loader',
          'css-loader'
        ]
      }
    ]
  }
};

我們來創(chuàng)建一個 CSS 文件:

$ cd src && touch style.css

src/style.css

.hello {
  color: red;
}

src/index.js

import _ from 'lodash';
import './style.css'; // 通過`import`引入 CSS 文件

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  element.classList.add('hello'); // 在相應元素上添加類名
    
  return element;
}

document.body.appendChild(component());

執(zhí)行npm run build饼丘,然后打開index.html趁桃,就可以看到紅色的字體了。CSS 文件此時已經(jīng)被打包到 bundle.js 中肄鸽。再打開瀏覽器控制臺卫病,就可以看到 webpack 做了些什么。

加載圖片

$ npm install --save-dev file-loader

file-loader 指示 webpack 以文件格式發(fā)出所需對象并返回文件的公共URL典徘,可用于任何文件的加載蟀苛。

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js', 
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      { // 增加加載圖片的規(guī)則
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
};

我們在當前項目的目錄中如下增加圖片:

  webpack-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
+   |- icon.jpg
    |- style.css
    |- index.js
  |- /node_modules

src/index.js

import _ from 'lodash';
import './style.css';
import Icon from './icon.jpg'; // Icon 是圖片的 URL

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  element.classList.add('hello');
  
  const myIcon = new Image();
  myIcon.src = Icon;

  element.appendChild(myIcon);
  
  return element;
}

document.body.appendChild(component());

src/style.css

.hello {
  color: red;
  background: url(./icon.jpg);
}

npm run build之。現(xiàn)在你可以看到單獨的圖片和以圖片為基礎的背景圖了逮诲。

加載字體

加載字體用的也是 file-loader帜平。

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js', 
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      },
      { // 增加加載字體的規(guī)則
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
};

在當前項目的目錄中如下增加字體:

  webpack-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
+   |- my-font.ttf
    |- icon.jpg
    |- style.css
    |- index.js
  |- /node_modules

src/style.css

@font-face {
  font-family: MyFont;
  src: url(./my-font.ttf);
}

.hello {
  color: red;
  background: url(./icon.jpg);
  font-family: MyFont;
}

運行打包命令之后便可以看到打包好的文件和發(fā)生改變的頁面。

加載 JSON 文件

因為 webpack 對 JSON 文件的支持是內(nèi)置的梅鹦,所以可以直接添加裆甩。

src/data.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "author": "Sam Yang"
}

src/index.js

import _ from 'lodash';
import './style.css';
import Icon from './icon.jpg';
import Data from './data.json'; // Data 變量包含可直接使用的 JSON 解析得到的對象

function component() {
  const element = document.createElement('div');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  element.classList.add('hello');

  const myIcon = new Image();
  myIcon.src = Icon;

  element.appendChild(myIcon);

  console.log(Data);
    
  return element;
}

document.body.appendChild(component());

關于其他文件的加載,可以尋求相應的 loader齐唆。

輸出管理

前面我們只有一個輸入文件嗤栓,但現(xiàn)實是我們往往有不止一個輸入文件,這時我們就需要輸入多個入口文件并管理輸出文件蝶念。我們在 src 目錄下增加一個 print.js 文件抛腕。

src/print.js

export default function printMe() {
  console.log('I get called from print.js!');
}

src/index.js

import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

function component() {
  const element = document.createElement('div');
  const btn = document.createElement('button');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  // element.classList.add('hello');

  // const myIcon = new Image();
  // myIcon.src = Icon;

  // element.appendChild(myIcon);

  // console.log(Data);

  btn.innerHTML = 'Click me and check the console!';
  btn.onclick = printMe;

  element.appendChild(btn);
    
  return element;
}

document.body.appendChild(component());

dist/index.html

<!DOCTYPE html>
<html>
<head>
    <title>webpack demo</title>
    <script src="./print.bundle.js"></script>
</head>
<body>
    <!-- <script src="bundle.js"></script> -->
    <script src="./app.bundle.js"></script>
</body>
</html>

webpack.config.js

const path = require('path');

module.exports = {
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js', // 根據(jù)入口起點名動態(tài)生成 bundle 名芋绸,可以使用像 "js/[name]/bundle.js" 這樣的文件夾結(jié)構(gòu)
    path: path.resolve(__dirname, 'dist')
  },
  // ...
};

Tips
filename: '[name].bundle.js'中的[name]會替換為對應的入口起點名,其他可用的替換請參見 output.filename担敌。

現(xiàn)在可以打包文件了摔敛。但是如果我們修改了入口文件名或增加了入口文件,index.html是不會自動引用新文件的全封,而手動修改實在太挫马昙。是時候使用插件 (plugin) 來完成這一任務了。我們使用 HtmlWebpackPlugin 自動生成 html 文件刹悴。

loader 和 plugin行楞,有什么區(qū)別?
loader (加載器)土匀,重在“加載”二字子房,是用于預處理文件的,只用于在加載不同類型的文件時對不同類型的文件做相應的處理就轧。而 plugin (插件)证杭,顧名思義,是用來增加 webpack 的功能的妒御,作用于整個 webpack 的構(gòu)建過程解愤。在 webpack 這個大公司中,loader 是保安大叔乎莉,負責對進入公司的不同人員的處理送讲,而 plugin 則是公司里不同職位的職員,負責公司里的各種不同業(yè)務惋啃,每增加一種新型的業(yè)務需求哼鬓,我們就需要增加一種 plugin。

安裝插件:

$ npm i --save-dev html-webpack-plugin

webpack.config.js

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

module.exports = {
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [ // 插件屬性肥橙,是插件的實例數(shù)組
    new HtmlWebpackPlugin({
      title: 'webpack demo',  // 生成 HTML 文檔的標題
      filename: 'index.html' // 寫入 HTML 文件的文件名魄宏,默認 `index.html`
    })
  ],
  // ...
};

你可以先把 dist 文件夾的index.html文件刪除,然后執(zhí)行打包命令存筏。咻咻咻宠互,我們看到 dist 目錄下已經(jīng)自動生成了一個index.html文件,但即使不刪除原先的index.html椭坚,該插件默認生成的index.html也會替換原本的index.html予跌。

此刻,當你細細觀察 dist 目錄時善茎,雖然現(xiàn)在生成了新的打包文件券册,但原本的打包文件bundle.js及其他不用的文件仍然存在在 dist 目錄中,所以在每次構(gòu)建前我們需要晴空 dist 目錄,我們使用 CleanWebpackPlugin 插件烁焙。

$ npm i clean-webpack-plugin --save-dev

webpack.config.js

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

module.exports = {
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']) // 第一個參數(shù)是要清理的目錄的字符串數(shù)組
  ],
  // ...
};

打包之航邢,現(xiàn)在,dist 中只存在打包生成的文件骄蝇。

開發(fā)環(huán)境

webpack 提供了很多便于開發(fā)時使用的功能膳殷,來一一看看吧。

使用代碼映射 (source map)

當你的代碼被打包后九火,如果打包后的代碼發(fā)生了錯誤赚窃,你很難追蹤到錯誤發(fā)生的原始位置,這個時候岔激,我們就需要代碼映射 (source map) 這種工具勒极,它能將編譯后的代碼映射回原始的源碼,你的錯誤是起源于打包前的b.js的某個位置虑鼎,代碼映射就能告訴你錯誤是那個模塊的那個位置辱匿。webpack 默認提供了 10 種風格的代碼映射,使用它們會明顯影響到構(gòu)建 (build) 和重構(gòu)建 (rebuild震叙,每次修改后需要重新構(gòu)建) 的速度,十種風格的差異可以參看 devtool戚丸。關于如何選擇映射風格可以參看 Webpack devtool source map。這里限府,我們?yōu)榱藴蚀_顯示錯誤位置,選擇速度較慢的inline-source-map诺舔。

webpack.config.js

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

module.exports = {
  devtool: 'inline-source-map', // 控制是否生成以及如何生成 source map
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  // ...
};

現(xiàn)在來手動制造一些錯誤:

src/print.js

  export default function printMe() {
-   console.log('I get called from print.js!');
+   cosnole.log('I get called from print.js!');
  }

打包之后打開index.html再點擊按鈕,你就會看到控制臺顯示如下報錯:

 Uncaught ReferenceError: cosnole is not defined
    at HTMLButtonElement.printMe (print.js:2)

現(xiàn)在残家,我們很清楚哪里發(fā)生了錯誤烁涌,然后輕松地改正之瑞信。

使用 webpack-dev-server

你一定有這樣的體驗筐眷,開發(fā)時每次修改代碼保存后都需要重新手動構(gòu)建代碼并手動刷新瀏覽器以觀察修改效果,這是很麻煩的垫毙,所以霹疫,我們要實時加載代碼∽劢妫可喜的是丽蝎,webpack 提供了對實時加載代碼的支持。我們需要安裝 webpack-dev-server 以獲得支持毫痕。

$ npm i --save-dev webpack-dev-server

webpack.config.js

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

module.exports = {
  devtool: 'inline-source-map',
  devServer: { // 檢測代碼變化并自動重新編譯并自動刷新瀏覽器
    contentBase: path.resolve(__dirname, 'dist') // 設置靜態(tài)資源的根目錄
  },
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  // ...
};

package.json

{
  ...
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server --open"
  },
  ...
}

Tips
使用 webpack-dev-server 時征峦,webpack 并沒有將所有生成的文件寫入磁盤,而是放在內(nèi)存中消请,提供更快的內(nèi)存內(nèi)訪問,便于實時更新类腮。

現(xiàn)在臊泰,可以直接運行npm start (start是 npm scripts 的內(nèi)置屬性,可直接運行)蚜枢,然后瀏覽器自動加載應用的頁面缸逃,默認在localhost:8080顯示。

模塊熱替換 (HMR, Hot Module Replacement)

webpack 提供了對模塊熱替換 (或者叫熱加載) 的支持厂抽。這一特性能夠讓應用運行的時候替換需频、增加或刪除模塊,而無需進行完全的重載筷凤。想進一步地了解其工作機理昭殉,可以參見 Hot Module Replacement苞七,但這并不是必需的,你可以選擇跳過機理部分繼續(xù)往下閱讀挪丢。

Tips
模塊熱替換(HMR)只更新發(fā)生變更(替換蹂风、添加、刪除)的模塊乾蓬,而無需重新加載整個頁面(實時加載惠啄,LiveReload),這樣可以顯著加快開發(fā)速度任内,一旦打開了 webpack-dev-server 的 hot 模式撵渡,在試圖重新加載整個頁面之前,熱模式會嘗試使用 HMR 來更新死嗦。

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack'); // 引入 webpack 便于調(diào)用其內(nèi)置插件

module.exports = {
  devtool: 'inline-source-map',
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    hot: true, // 告訴 dev-server 我們在用 HMR
    hotOnly: true // 指定如果熱加載失敗了禁止刷新頁面 (這是 webpack 的默認行為)趋距,這樣便于我們知道失敗是因為何種錯誤
  },
  // entry: './src/index.js',
  entry: {
    app: './src/index.js',
    // print: './src/print.js'
  },
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    new webpack.HotModuleReplacementPlugin(), // 啟用 HMR
    new webpack.NamedModulesPlugin() // 打印日志信息時 webpack 默認使用模塊的數(shù)字 ID 指代模塊,不便于 debug越走,這個插件可以將其替換為模塊的真實路徑
  ],
  // ...
};

Tips
webpack-dev-server 會為每個入口文件創(chuàng)建一個客戶端腳本棚品,這個腳本會監(jiān)控該入口文件的依賴模塊的更新,如果該入口文件編寫了 HMR 處理函數(shù)廊敌,它就能接收依賴模塊的更新铜跑,反之,更新會向上冒泡骡澈,直到客戶端腳本仍沒有處理函數(shù)的話锅纺,webpack-dev-server 會重新加載整個頁面。如果入口文件本身發(fā)生了更新肋殴,因為向上會冒泡到客戶端腳本囤锉,并且不存在 HMR 處理函數(shù),所以會導致頁面重載护锤。

我們已經(jīng)開啟了 HMR 的功能官地,HMR 的接口已經(jīng)暴露在module.hot屬性之下,我們只需要調(diào)用 HMR API 即可實現(xiàn)熱加載烙懦。當“被加載模塊”發(fā)生改變時驱入,依賴該模塊的模塊便能檢測到改變并接收改變之后的模塊。

src/index.js

import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

function component() {
  const element = document.createElement('div');
  const btn = document.createElement('button');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  // element.classList.add('hello');

  // const myIcon = new Image();
  // myIcon.src = Icon;

  // element.appendChild(myIcon);

  // console.log(Data);

  btn.innerHTML = 'Click me and check the console!';
  btn.onclick = printMe;

  element.appendChild(btn);
    
  return element;
}

document.body.appendChild(component());

if(module.hot) { // 習慣上我們會檢查是否可以訪問 `module.hot` 屬性
  module.hot.accept('./print.js', function() { // 接受給定依賴模塊的更新氯析,并觸發(fā)一個回調(diào)函數(shù)來對這些更新做出響應
    console.log('Accepting the updated printMe module!');
    printMe();
  });
}

npm start之亏较。為了演示效果,我們做如下修改:

src/print.js

  export default function printMe() {
-   console.log('I get called from print.js!');
+   console.log('Updating print.js...');
  }

我們會看到控制臺打印出的信息中含有以下幾行:

index.js:33 Accepting the updated printMe module!
print.js:2 Updating print.js...
log.js:23 [HMR] Updated modules:
log.js:23 [HMR]  - ./src/print.js
log.js:23 [HMR] App is up to date.

Tips
webpack-dev-server 在 inline mode (此為默認模式) 時掩缓,會為每個入口起點 (entry) 創(chuàng)建一個客戶端腳本雪情,所以你會在上面的輸出中看到有些信息重復輸出兩次。

但是當你點擊頁面的按鈕時你辣,你會發(fā)現(xiàn)控制臺輸出的是舊的printMe函數(shù)輸出的信息巡通,因為onclick事件綁定的仍是原始的printMe函數(shù)尘执。我們需要在module.hot.accept里更新綁定。

src/index.js

import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

// ...

// document.body.appendChild(component());
var element = component();
document.body.appendChild(element);

if(module.hot) {
  module.hot.accept('./print.js', function() {
    console.log('Accepting the updated printMe module!');
    // printMe();
    
    document.body.removeChild(element);
    element = component();
    document.body.appendChild(element);
  });
}

Tips
uglifyjs-webpack-plugin 升級到 v0.4.6 時無法正確壓縮 ES6 的代碼扁达,所以上面有些代碼采用 ES5 以暫時方便后面的壓縮正卧,詳見 #49

模塊熱替換也可以用于樣式的修改跪解,效果跟控制臺修改一樣一樣的炉旷。

src/index.js

import _ from 'lodash';
import printMe from './print.js';
import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

// ...

npm start之,做如下修改:

/* ... */

body {
  background-color: yellow;
}

可以發(fā)現(xiàn)在不重載頁面的前提下我們對樣式的修改進行了熱加載叉讥,棒窘行!

生產(chǎn)環(huán)境

自動方式

我們只需要運行webpack -p (相當于 webpack --optimize-minimize --define process.env.NODE_ENV="'production'")這個命令,便可以自動構(gòu)建生產(chǎn)版本的應用图仓,這個命令會完成以下步驟:

  • 使用 UglifyJsPlugin (webpack.optimize.UglifyJsPlugin) 壓縮 JS 文件 (此插件和 uglifyjs-webpack-plugin 相同)
  • 運行 LoaderOptionsPlugin 插件罐盔,這個插件是用來遷移的,見 document
  • 設置 NodeJS 的環(huán)境變量救崔,觸發(fā)某些 package 包以不同方式編譯

值得一提的是惶看,webpack -p設置的process.env.NODE_ENV環(huán)境變量,是用于編譯后的代碼的六孵,只有在打包后的代碼中纬黎,這一環(huán)境變量才是有效的。如果在 webpack 配置文件中引用此環(huán)境變量劫窒,得到的是 undefined本今,可以參見 #2537。但是主巍,有時我們確實需要在 webpack 配置文件中使用 process.env.NODE_ENV冠息,怎么辦呢?一個方法是運行NODE_ENV='production' webpack -p命令孕索,不過這個命令在Windows中是會出問題的逛艰。為了解決兼容問題,我們采用 cross-env 解決跨平臺的問題搞旭。

$ npm i --save-dev cross-env

package.json

{
  ...
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack -p",
    "start": "webpack-dev-server --open"
  },
  ...
}

現(xiàn)在可以在配置文件中使用process.env.NODE_ENV了瓮孙。

webpack.config.js

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

module.exports = {
  // ...
  output: {
    // filename: 'bundle.js',
    // filename: '[name].bundle.js',
    filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js', // 在配置文件中使用`process.env.NODE_ENV`
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    // new webpack.HotModuleReplacementPlugin(), // 關閉 HMR 功能
    new webpack.NamedModulesPlugin()
  ],
  // ...
};

Tips
[chunkhash]不能和 HMR 一起使用,換句話說选脊,不應該在開發(fā)環(huán)境中使用 [chunkhash] (或者 [hash]),這會導致許多問題脸甘。詳情見 #2393#377恳啥。

build 之,我們得到了生產(chǎn)版本的壓縮好的打包文件丹诀。

多配置文件配置

有時我們會需要為不同的環(huán)境配置不同的配置文件钝的,可以選擇 簡易方法翁垂,這里我們采用較為先進的方法。先準備一個基本的配置文件硝桩,包含了所有環(huán)境都包含的配置沿猜,然后用 webpack-merge 將它和特定環(huán)境的配置文件合并并導出,這樣就減少了基本配置的重復碗脊。

$ npm i --save-dev webpack-merge

webpack.common.js

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

module.exports = {
  entry: {
    app: './src/index.js',
    print: './src/print.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist'])
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          'file-loader'
        ]
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  }
};

webpack.dev.js

const path = require('path');
const webpack = require('webpack');
const Merge = require('webpack-merge');
const CommonConfig = require('./webpack.common.js');

module.exports = Merge(CommonConfig, {
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    hot: true,
    hotOnly: true
  },
  output: {
    filename: '[name].bundle.js'
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development') // 在編譯的代碼里設置了`process.env.NODE_ENV`變量
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ]
});

webpack.prod.js

const path = require('path');
const webpack = require('webpack');
const Merge = require('webpack-merge');
const CommonConfig = require('./webpack.common.js');

module.exports = Merge(CommonConfig, {
  devtool: 'cheap-module-source-map',
  output: {
    filename: '[name].[chunkhash].js'
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new webpack.optimize.UglifyJsPlugin()
  ]
});

package.json

{
  ...
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack -p",
    "start": "webpack-dev-server --open",
    "build:dev": "webpack-dev-server --open --config webpack.dev.js",
    "build:prod": "webpack --progress --config webpack.prod.js"
  },
  ...
}

現(xiàn)在只需執(zhí)行npm run build:devnpm run build:prod便可以得到開發(fā)版或者生產(chǎn)版了啼肩!

Tips
webpack 命令行選項見 Command Line Interface

代碼分離

入口分離

我們先創(chuàng)建一個新文件:

$ cd src && touch another.js

src/another.js

import _ from 'lodash';

console.log(_.join(['Another', 'module', 'loaded!'], ' '));

webpack.config.js

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

module.exports = {
  // ...
  entry: {
    app: './src/index.js',
    // print: './src/print.js'
    another: './src/another.js'
  },
  // ...
};

cd .. && npm run build之衙伶,我們發(fā)現(xiàn)用入口分離的代碼得到了兩個大文件祈坠,這是因為兩個入口文件都引入了lodash,這很大程度上造成了冗余矢劲,在同一個頁面中我們只需要引入一個lodash就可以了赦拘。

抽取相同部分

我們使用 CommonsChunkPlugin 插件來將相同的部分提取出來放到一個單獨的模塊中。

webpack.config.js

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

module.exports = {
  // devtool: 'inline-source-map',
  // ...
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common' // 抽取出的模塊的模塊名
    }),
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

build 之芬沉,可以看到結(jié)果中包含以下部分:

    app.bundle.js    6.14 kB       0  [emitted]  app
another.bundle.js  185 bytes       1  [emitted]  another
 common.bundle.js    73.2 kB       2  [emitted]  common
       index.html  314 bytes          [emitted]

我們把lodash分離出來了躺同。

動態(tài)引入

我們還可以選擇以動態(tài)引入的方式來實現(xiàn)代碼分離,借助 import() 實現(xiàn)之丸逸。

webpack.config.js

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

module.exports = {
  // ...
  entry: {
    app: './src/index.js',
    // print: './src/print.js'
    // another: './src/another.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].bundle.js',
    chunkFilename: '[name].bundle.js', // 指定非入口塊文件輸出的名字
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist'])
    // new webpack.optimize.CommonsChunkPlugin({
    //   name: 'common'
    // }),
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

src/index.js

// import _ from 'lodash';
import printMe from './print.js';
// import './style.css';
// import Icon from './icon.jpg';
// import Data from './data.json';

function component() {
  // 此函數(shù)原來的內(nèi)容全部注釋掉...

  return import(/* webpackChunkName: "lodash" */ 'lodash').then(function(_) {
    const element = document.createElement('div');
    const btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;

    element.appendChild(btn);

    return element;
  }).catch(function(error) {
    console.log('An error occurred while loading the component')
  });
}

// document.body.appendChild(component());
// var element = component();
// document.body.appendChild(element);

// 原本熱加載的部分全部注釋掉...

component().then(function(component) {
   document.body.appendChild(component);
 });

Tips
注意上面中的/* webpackChunkName: "lodash" */這段注釋蹋艺,它并不是可有可無的,它能幫助我們結(jié)合output.chunkFilename把分離出的模塊最終命名為lodash.bundle.js而非[id].bundle.js椭员。

現(xiàn)在 build 之看看吧车海。

懶加載 (lazy loading)

既然有了import(),我們可以選擇在需要的時候才加載相應的模塊隘击,減少了應用初始化時加載大量暫不需要的模塊的壓力侍芝,這能讓我們的應用更高效地運行。

src/print.js

console.log('The print.js module has loaded! See the network tab in dev tools...');

export default function printMe() {
  // console.log('Updating print.js...');
  console.log('Button Clicked: Here\'s "some text"!');
}

src/index.js

import _ from 'lodash';
// 其他引入注釋...

function component() {
  const element = document.createElement('div');
  const btn = document.createElement('button');
    
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  // element.classList.add('hello');

  // const myIcon = new Image();
  // myIcon.src = Icon;

  // element.appendChild(myIcon);

  // console.log(Data);

  btn.innerHTML = 'Click me and check the console!';
  // btn.onclick = printMe;

  element.appendChild(btn);

  btn.onclick = function() {
    import(/* webpackChunkName: "print" */ './print')
    .then(function(module) {
      const printMe = module.default; // 引入模塊的默認函數(shù)

      printMe();
    });
  };
    
  return element;

  // 原本的動態(tài)引入注釋...
}

document.body.appendChild(component());
// var element = component();
// document.body.appendChild(element);

// 熱加載部分注釋

// component().then(function(component) {
//    document.body.appendChild(component);
//  });

構(gòu)建之埋同,控制臺此時并無輸出州叠,點擊按鈕,會看到控制臺如下輸出:

print.bundle.js:1 The print.js module has loaded! See the network tab in dev tools...
print.bundle.js:1 Button Clicked: Here's "some text"!

說明 print 模塊只在我們點擊時才引入了凶赁,すっげえ咧栗!

緩存 (caching)

瀏覽器在初次加載網(wǎng)站時,會下載很多文件虱肄,為了較少下載大量資源的壓力致板,瀏覽器會對資源進行緩存 (caching),這樣瀏覽器便可以更迅速地加載網(wǎng)站咏窿,但是我們需要在文件內(nèi)容發(fā)生改變時更新文件斟或。

我們可以在輸出文件名上下手腳:

webpack.config.js

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

module.exports = {
  // ...
  output: {
    // filename: 'bundle.js',
    filename: '[name].[chunkhash].js',
    // chunkFilename: '[name].bundle.js',
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  // ...
};

Tips
[chunkhash] 是內(nèi)容相關的,只要內(nèi)容發(fā)生了改變集嵌,構(gòu)建后文件名的 hash 就會發(fā)生改變萝挤。

還有一個要點是提取出第三方庫放到單獨模塊中御毅,因為它們是不太可能頻繁發(fā)生改變的,所以無需多次加載這些模塊怜珍,提取的方法用 CommonsChunkPlugin 插件端蛆,這個插件上文中提到過,指定入口文件名時它會提取改入口文件為單個文件酥泛,不指定則會提取 webpack 的運行時代碼今豆。

webpack.config.js

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

module.exports = {
  // ...
  entry: {
    app: './src/index.js',
    vendor: [ // 第三方庫可以統(tǒng)一放在這個入口一起合并
      'lodash'
    ]
    // print: './src/print.js'
    // another: './src/another.js'
  },
  output: {
    // filename: 'bundle.js',
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].bundle.js',
    // filename: process.env.NODE_ENV === 'production' ? '[name].[chunkhash].js' : '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor' // 將 vendor 入口處的代碼放入 vendor 模塊
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime' // 將 webpack 自身的運行時代碼放在 runtime 模塊
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

Tips
包含 vendor 的 CommonsChunkPlugin 實例必須在包含 runtime 的之前,否則會報錯揭璃。

src/index.js

// import _ from 'lodash';
// ...

// ...

如果我們在 src 下新建一個文件h.js晚凿,再在index.js中引入它,保存瘦馍,構(gòu)建之歼秽,我們發(fā)現(xiàn)有些沒改變的模塊的 hash 也發(fā)生了改變,這是因為加入h.js后它們的module.id變了情组,但這明顯是不合理的燥筷。在開發(fā)環(huán)境,我們可以用 NamedModulesPlugin 將 id 換成具體路徑名院崇。而在生產(chǎn)環(huán)境肆氓,我們可以使用 HashedModuleIdsPlugin

webpack.config.js

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

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new webpack.HashedModuleIdsPlugin(), // 替換掉原來的`module.id`
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

再來執(zhí)行剛才那波操作底瓣,就會發(fā)現(xiàn)無關修改的模塊 hash 未變了谢揪。

Shimming

Tips
你可以將 shim 簡單理解為是用于兼容 API 的小型庫。

使用 jQuery 時我們習慣性地使用$jQuery變量捐凭,每次都使用const $ = require(“jquery”)引入的話太麻煩拨扶,如果能直接把這兩個變量設置為全局變量豈不美滋滋?這樣就可以在每個模塊中直接使用這兩個變量了茁肠。為了兼容這一做法患民,我們使用 ProvidePlugin 插件為我們完成這一任務。

$ npm i --save jquery

webpack.config.js

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

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack demo',
      filename: 'index.html'
    }),
    new webpack.ProvidePlugin({ // 設置全局變量
      $: 'jquery',
      jQuery: 'jquery'
    }),
    new webpack.HashedModuleIdsPlugin(),
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

src/print.js

console.log('The print.js module has loaded! See the network tab in dev tools...');
console.log($('title').text()); // 使用 jQuery

export default function printMe() {
  // console.log('Updating print.js...');
  console.log('Button Clicked: Here\'s "some text"!');
}

build垦梆,點擊頁面按鈕匹颤,成功了。

另外托猩,如果你需要在某些模塊加載時設置該模塊的全局變量印蓖,請看 這里

結(jié)尾的一點廢話

終于寫完了 :)京腥,也感謝你能耐心看到這里另伍。webpack 這個工具的配置還是有些麻煩的。但是呢,某人說這個東東前期會花比較多時間摆尝,后期會大大提高你的效率。所以呢因悲,還是拿下這個東東吧堕汞。有其他需求的話可以繼續(xù)看官方的文檔。遇到困難可以找:

我寫好的 demo 文件放在了這里晃琳。

Reference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市卫旱,隨后出現(xiàn)的幾起案子人灼,更是在濱河造成了極大的恐慌,老刑警劉巖顾翼,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件投放,死亡現(xiàn)場離奇詭異,居然都是意外死亡适贸,警方通過查閱死者的電腦和手機灸芳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拜姿,“玉大人烙样,你說我怎么就攤上這事∪锓剩” “怎么了谒获?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長壁却。 經(jīng)常有香客問我批狱,道長,這世上最難降的妖魔是什么儒洛? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任精耐,我火速辦了婚禮,結(jié)果婚禮上琅锻,老公的妹妹穿的比我還像新娘卦停。我一直安慰自己,他們只是感情好恼蓬,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布惊完。 她就那樣靜靜地躺著,像睡著了一般处硬。 火紅的嫁衣襯著肌膚如雪小槐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音凿跳,去河邊找鬼件豌。 笑死,一個胖子當著我的面吹牛控嗜,可吹牛的內(nèi)容都是我干的茧彤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼疆栏,長吁一口氣:“原來是場噩夢啊……” “哼曾掂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起壁顶,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤珠洗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后若专,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體许蓖,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年富岳,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛔糯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡窖式,死狀恐怖蚁飒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萝喘,我是刑警寧澤淮逻,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站阁簸,受9級特大地震影響爬早,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜启妹,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一鸥昏、第九天 我趴在偏房一處隱蔽的房頂上張望电抚。 院中可真熱鬧,春花似錦、人聲如沸粘我。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涩拙。三九已至,卻和暖如春丧慈,著一層夾襖步出監(jiān)牢的瞬間析命,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鹃愤,地道東北人簇搅。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像昼浦,于是被迫代替她去往敵國和親馍资。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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

  • GitChat技術雜談 前言 本文較長关噪,為了節(jié)省你的閱讀時間,在文前列寫作思路如下: 什么是 webpack乌妙,它要...
    蕭玄辭閱讀 12,671評論 7 110
  • 最近在學習 Webpack,網(wǎng)上大多數(shù)入門教程都是基于 Webpack 1.x 版本的,我學習 Webpack 的...
    My_Oh_My閱讀 8,166評論 40 247
  • 版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載泽艘。 webpack介紹和使用 一欲险、webpack介紹 1、由來 ...
    it筱竹閱讀 11,028評論 0 21
  • webpack 介紹 webpack 是什么 為什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert閱讀 6,450評論 2 71
  • 無意中看到zhangwnag大佬分享的webpack教程感覺受益匪淺匹涮,特此分享以備自己日后查看天试,也希望更多的人看到...
    小小字符閱讀 8,140評論 7 35