Webpack 3,從入門到放棄

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

Update (2017.8.27) : 關(guān)于 output.publicPath、devServer.contentBase、devServer.publicPath的區(qū)別。如下:

  • output.publicPath: 對(duì)于這個(gè)選項(xiàng),我們無(wú)需關(guān)注什么絕對(duì)相對(duì)路徑,因?yàn)閮煞N路徑都可以。我們只需要知道一點(diǎn):這個(gè)選項(xiàng)是指定 HTML 文件中資源文件 (字體、圖片、JS文件等) 的文件名的公共 URL 部分的。在實(shí)際情況中,我們首先會(huì)通過(guò)output.filename或有些 loader 如file-loadername屬性設(shè)置文件名的原始部分,webpack 將文件名的原始部分和公共部分結(jié)合之后,HTML 文件就能獲取到資源文件了。
  • devServer.contentBase: 設(shè)置靜態(tài)資源的根目錄,html-webpack-plugin生成的 html 不是靜態(tài)資源。當(dāng)用 html 文件里的地址無(wú)法找到靜態(tài)資源文件時(shí)就會(huì)去這個(gè)目錄下去找。
  • devServer.publicPath: 指定瀏覽器上訪問(wèn)所有 打包(bundled)文件 (在dist里生成的所有文件) 的根目錄,這個(gè)根目錄是相對(duì)服務(wù)器地址及端口的,比devServer.contentBaseoutput.publicPath優(yōu)先。

前言

Tips
如果你用過(guò) webpack 且一直用的是 webpack 1,請(qǐng)參考 從v1遷移到v2 (v2 和 v3 差異不大) 對(duì)版本變更的內(nèi)容進(jìn)行適當(dāng)?shù)牧私?,然后再選擇性地閱讀本文。

首先,這篇文章是根據(jù)當(dāng)前最新的 webpack 版本 (即 v3.4.1) 撰寫,較長(zhǎng)一段時(shí)間內(nèi)無(wú)需擔(dān)心過(guò)時(shí)的問(wèn)題。其次,這應(yīng)該會(huì)是一篇極長(zhǎng)的文章,涵蓋了基本的使用方法,有更高級(jí)功能的需求可以參考官方文檔繼續(xù)學(xué)習(xí)。再次,即使是基本的功能,也內(nèi)容繁多,我盡可能地解釋通俗易懂,將我學(xué)習(xí)過(guò)程中的疑惑和坑一一解釋,如有紕漏,敬請(qǐng)雅正。再次,為了清晰有效地講解,我會(huì)演示從零編寫 demo,只要一步步跟著做,就會(huì)清晰許多。最后,官方文檔也是個(gè)坑爹貨!

Webpack,何許人也?

借用官方的說(shuō)法:

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.

簡(jiǎn)言之,webpack 是一個(gè)模塊打包器 (module bundler),能夠?qū)⑷魏钨Y源如 JavaScript 文件、CSS 文件、圖片等打包成一個(gè)或少數(shù)文件。

為什么要用介個(gè) Webpack?

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

Tips
想了解合并 HTTP 請(qǐng)求的意義,請(qǐng)見 這里

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

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

webpack 是模塊打包器(module bundler),把所有的模塊打包成一個(gè)或少量文件,使你只需加載少量文件即可運(yùn)行整個(gè)應(yīng)用,而無(wú)需像之前那樣加載大量的圖片,css文件,js文件,字體文件等等。而gulp/grunt 是自動(dòng)化構(gòu)建工具,或者叫任務(wù)運(yùn)行器(task runner),是把你所有重復(fù)的手動(dòng)操作讓代碼來(lái)做,例如壓縮JS代碼、CSS代碼,代碼檢查、代碼編譯等等,自動(dòng)化構(gòu)建工具并不能把所有模塊打包到一起,也不能構(gòu)建不同模塊之間的依賴圖。兩者來(lái)比較的話,gulp/grunt 無(wú)法做模塊打包的事,webpack 雖然有 loader 和 plugin可以做一部分 gulp/grunt 能做的事,但是終究 webpack 的插件還是不如 gulp/grunt 的插件豐富,能做的事比較有限。于是有人兩者結(jié)合著用,將 webpack 放到 gulp/grunt 中用。然而,更好的方法是用 npm scripts 取代 gulp/grunt,npm 是 node 的包管理器 (node package manager),用于管理 node 的第三方軟件包,npm 對(duì)于任務(wù)命令的良好支持讓你最終省卻了編寫任務(wù)代碼的必要,取而代之的,是老祖宗的幾個(gè)命令行,僅靠幾句命令行就足以完成你的模塊打包和自動(dòng)化構(gòu)建的所有需求。

準(zhǔn)備開始

先來(lái)看看一個(gè) webpack 的一個(gè)完備的配置文件,是 介樣 的,當(dāng)然啦,這里面有很多配置項(xiàng)是即使到這個(gè)軟件被廢棄你也用不上的:),所以無(wú)需擔(dān)心。

基本配置

開始之前,請(qǐng)確定你已經(jīng)安裝了當(dā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ù)庫(kù) lodash 來(lái)演示我們的 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 的標(biāo)準(zhǔn),但是仍未得到大多數(shù)瀏覽器的支持 (可喜的是, Chrome 61 已經(jīng)開始默認(rèn)支持了,見 ES6 modules),不過(guò) webpack 提供了對(duì)這個(gè)特性的支持,但是除了這個(gè)特性,其他的 ES6 特性并不會(huì)得到 webpack 的特別支持,如有需要,須借助 Babel 進(jìn)行轉(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)在,我們運(yùn)行 webpack 來(lái)打包 index.jsbundle.js,本地安裝了 webpack 后可以通過(guò) node_modules/.bin/webpack 來(lái)訪問(wèn) webpack 的二進(jìn)制版本。

$ cd ..
$ ./node_modules/.bin/webpack src/index.js dist/bundle.js # 第一個(gè)參數(shù)是打包的入口文件,第二個(gè)參數(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)得到了你的第一個(gè)打包文件 (bundle.js) 了。

使用配置文件

像上面這樣使用 webpack 應(yīng)該是最挫的姿勢(shì)了,所以我們要使用 webpack 的配置文件來(lái)提高我們的姿勢(shì)水平。

$ touch webpack.config.js

webpack.config.js

const path = require('path');

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

執(zhí)行:

$ ./node_modules/.bin/webpack --config webpack.config.js # `--config` 制定 webpack 的配置文件,默認(rèn)是 `webpack.config.js`

所以這里可以省卻 --config webpack.config.js。但是每次都要寫 ./node_modules/.bin/webpack 實(shí)在讓人不爽,所以我們要?jiǎng)佑?NPM Scripts。

package.json

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

Tips
npm scripts 中我們可以通過(guò)包名直接引用本地安裝的 npm 包的二進(jìn)制版本,而無(wú)需編寫包的整個(gè)路徑。

執(zhí)行:

$ npm run build

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

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

打包其他類型的文件

因?yàn)槠渌募?JS 文件類型不同,要把他們加載到 JS 文件中就需要經(jīng)過(guò)加載器 (loader) 的處理。

加載 CSS

我們需要安裝兩個(gè) loader 來(lái)處理 CSS 文件:

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

style-loader 通過(guò)插入 <style> 標(biāo)簽將 CSS 加入到 DOM 中,css-loader 會(huì)像解釋 import/require() 一樣解釋 @import 和 url()。

const path = require('path');

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

我們來(lái)創(chuàng)建一個(gè) CSS 文件:

$ cd src && touch style.css

src/style.css

.hello {
  color: red;
}

src/index.js

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

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

document.body.appendChild(component());

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

加載圖片

$ npm install --save-dev file-loader

file-loader 指示 webpack 以文件格式發(fā)出所需對(duì)象并返回文件的公共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'
        ]
      }
    ]
  }
};

我們?cè)诋?dāng)前項(xiàng)目的目錄中如下增加圖片:

  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之?,F(xiàn)在你可以看到單獨(dú)的圖片和以圖片為基礎(chǔ)的背景圖了。

加載字體

加載字體用的也是 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'
        ]
      }
    ]
  }
};

在當(dāng)前項(xiàng)目的目錄中如下增加字體:

  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;
}

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

加載 JSON 文件

因?yàn)?webpack 對(duì) 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 解析得到的對(duì)象

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());

關(guān)于其他文件的加載,可以尋求相應(yīng)的 loader。

輸出管理

前面我們只有一個(gè)輸入文件,但現(xiàn)實(shí)是我們往往有不止一個(gè)輸入文件,這時(shí)我們就需要輸入多個(gè)入口文件并管理輸出文件。我們?cè)?src 目錄下增加一個(gè) 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ù)入口起點(diǎn)名動(dòng)態(tài)生成 bundle 名,可以使用像 "js/[name]/bundle.js" 這樣的文件夾結(jié)構(gòu)
    path: path.resolve(__dirname, 'dist')
  },
  // ...
};

Tips
filename: '[name].bundle.js'中的[name]會(huì)替換為對(duì)應(yīng)的入口起點(diǎn)名,其他可用的替換請(qǐng)參見 output.filename。

現(xiàn)在可以打包文件了。但是如果我們修改了入口文件名或增加了入口文件,index.html是不會(huì)自動(dòng)引用新文件的,而手動(dòng)修改實(shí)在太挫。是時(shí)候使用插件 (plugin) 來(lái)完成這一任務(wù)了。我們使用 HtmlWebpackPlugin 自動(dòng)生成 html 文件。

loader 和 plugin,有什么區(qū)別?
loader (加載器),重在“加載”二字,是用于預(yù)處理文件的,只用于在加載不同類型的文件時(shí)對(duì)不同類型的文件做相應(yīng)的處理。而 plugin (插件),顧名思義,是用來(lái)增加 webpack 的功能的,作用于整個(gè) webpack 的構(gòu)建過(guò)程。在 webpack 這個(gè)大公司中,loader 是保安大叔,負(fù)責(zé)對(duì)進(jìn)入公司的不同人員的處理,而 plugin 則是公司里不同職位的職員,負(fù)責(zé)公司里的各種不同業(yè)務(wù),每增加一種新型的業(yè)務(wù)需求,我們就需要增加一種 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í)例數(shù)組
    new HtmlWebpackPlugin({
      title: 'webpack demo',  // 生成 HTML 文檔的標(biāo)題
      filename: 'index.html' // 寫入 HTML 文件的文件名,默認(rèn) `index.html`
    })
  ],
  // ...
};

你可以先把 dist 文件夾的index.html文件刪除,然后執(zhí)行打包命令。咻咻咻,我們看到 dist 目錄下已經(jīng)自動(dòng)生成了一個(gè)index.html文件,但即使不刪除原先的index.html,該插件默認(rèn)生成的index.html也會(huì)替換原本的index.html。

此刻,當(dāng)你細(xì)細(xì)觀察 dist 目錄時(shí),雖然現(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']) // 第一個(gè)參數(shù)是要清理的目錄的字符串?dāng)?shù)組
  ],
  // ...
};

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

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

webpack 提供了很多便于開發(fā)時(shí)使用的功能,來(lái)一一看看吧。

使用代碼映射 (source map)

當(dāng)你的代碼被打包后,如果打包后的代碼發(fā)生了錯(cuò)誤,你很難追蹤到錯(cuò)誤發(fā)生的原始位置,這個(gè)時(shí)候,我們就需要代碼映射 (source map) 這種工具,它能將編譯后的代碼映射回原始的源碼,你的錯(cuò)誤是起源于打包前的b.js的某個(gè)位置,代碼映射就能告訴你錯(cuò)誤是那個(gè)模塊的那個(gè)位置。webpack 默認(rèn)提供了 10 種風(fēng)格的代碼映射,使用它們會(huì)明顯影響到構(gòu)建 (build) 和重構(gòu)建 (rebuild,每次修改后需要重新構(gòu)建) 的速度,十種風(fēng)格的差異可以參看 devtool。關(guān)于如何選擇映射風(fēng)格可以參看 Webpack devtool source map。這里,我們?yōu)榱藴?zhǔn)確顯示錯(cuò)誤位置,選擇速度較慢的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)在來(lái)手動(dòng)制造一些錯(cuò)誤:

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再點(diǎn)擊按鈕,你就會(huì)看到控制臺(tái)顯示如下報(bào)錯(cuò):

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

現(xiàn)在,我們很清楚哪里發(fā)生了錯(cuò)誤,然后輕松地改正之。

使用 webpack-dev-server

你一定有這樣的體驗(yàn),開發(fā)時(shí)每次修改代碼保存后都需要重新手動(dòng)構(gòu)建代碼并手動(dòng)刷新瀏覽器以觀察修改效果,這是很麻煩的,所以,我們要實(shí)時(shí)加載代碼。可喜的是,webpack 提供了對(duì)實(shí)時(shí)加載代碼的支持。我們需要安裝 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: { // 檢測(cè)代碼變化并自動(dòng)重新編譯并自動(dòng)刷新瀏覽器
    contentBase: path.resolve(__dirname, 'dist') // 設(shè)置靜態(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 時(shí),webpack 并沒(méi)有將所有生成的文件寫入磁盤,而是放在內(nèi)存中,提供更快的內(nèi)存內(nèi)訪問(wèn),便于實(shí)時(shí)更新。

現(xiàn)在,可以直接運(yùn)行npm start (start是 npm scripts 的內(nèi)置屬性,可直接運(yùn)行),然后瀏覽器自動(dòng)加載應(yīng)用的頁(yè)面,默認(rèn)在localhost:8080顯示。

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

webpack 提供了對(duì)模塊熱替換 (或者叫熱加載) 的支持。這一特性能夠讓應(yīng)用運(yùn)行的時(shí)候替換、增加或刪除模塊,而無(wú)需進(jìn)行完全的重載。想進(jìn)一步地了解其工作機(jī)理,可以參見 Hot Module Replacement,但這并不是必需的,你可以選擇跳過(guò)機(jī)理部分繼續(xù)往下閱讀。

Tips
模塊熱替換(HMR)只更新發(fā)生變更(替換、添加、刪除)的模塊,而無(wú)需重新加載整個(gè)頁(yè)面(實(shí)時(shí)加載,LiveReload),這樣可以顯著加快開發(fā)速度,一旦打開了 webpack-dev-server 的 hot 模式,在試圖重新加載整個(gè)頁(yè)面之前,熱模式會(huì)嘗試使用 HMR 來(lái)更新。

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 我們?cè)谟?HMR
    hotOnly: true // 指定如果熱加載失敗了禁止刷新頁(yè)面 (這是 webpack 的默認(rèn)行為),這樣便于我們知道失敗是因?yàn)楹畏N錯(cuò)誤
  },
  // 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() // 打印日志信息時(shí) webpack 默認(rèn)使用模塊的數(shù)字 ID 指代模塊,不便于 debug,這個(gè)插件可以將其替換為模塊的真實(shí)路徑
  ],
  // ...
};

Tips
webpack-dev-server 會(huì)為每個(gè)入口文件創(chuàng)建一個(gè)客戶端腳本,這個(gè)腳本會(huì)監(jiān)控該入口文件的依賴模塊的更新,如果該入口文件編寫了 HMR 處理函數(shù),它就能接收依賴模塊的更新,反之,更新會(huì)向上冒泡,直到客戶端腳本仍沒(méi)有處理函數(shù)的話,webpack-dev-server 會(huì)重新加載整個(gè)頁(yè)面。如果入口文件本身發(fā)生了更新,因?yàn)橄蛏蠒?huì)冒泡到客戶端腳本,并且不存在 HMR 處理函數(shù),所以會(huì)導(dǎo)致頁(yè)面重載。

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

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) { // 習(xí)慣上我們會(huì)檢查是否可以訪問(wèn) `module.hot` 屬性
  module.hot.accept('./print.js', function() { // 接受給定依賴模塊的更新,并觸發(fā)一個(gè)回調(diào)函數(shù)來(lái)對(duì)這些更新做出響應(yīng)
    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...');
  }

我們會(huì)看到控制臺(tái)打印出的信息中含有以下幾行:

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 (此為默認(rèn)模式) 時(shí),會(huì)為每個(gè)入口起點(diǎn) (entry) 創(chuàng)建一個(gè)客戶端腳本,所以你會(huì)在上面的輸出中看到有些信息重復(fù)輸出兩次。

但是當(dāng)你點(diǎn)擊頁(yè)面的按鈕時(shí),你會(huì)發(fā)現(xiàn)控制臺(tái)輸出的是舊的printMe函數(shù)輸出的信息,因?yàn)?code>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 升級(jí)到 v0.4.6 時(shí)無(wú)法正確壓縮 ES6 的代碼,所以上面有些代碼采用 ES5 以暫時(shí)方便后面的壓縮,詳見 #49。

模塊熱替換也可以用于樣式的修改,效果跟控制臺(tái)修改一樣一樣的。

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)在不重載頁(yè)面的前提下我們對(duì)樣式的修改進(jìn)行了熱加載,棒!

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

自動(dòng)方式

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

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

值得一提的是,webpack -p設(shè)置的process.env.NODE_ENV環(huán)境變量,是用于編譯后的代碼的,只有在打包后的代碼中,這一環(huán)境變量才是有效的。如果在 webpack 配置文件中引用此環(huán)境變量,得到的是 undefined,可以參見 #2537。但是,有時(shí)我們確實(shí)需要在 webpack 配置文件中使用 process.env.NODE_ENV,怎么辦呢?一個(gè)方法是運(yùn)行NODE_ENV='production' webpack -p命令,不過(guò)這個(gè)命令在Windows中是會(huì)出問(wèn)題的。為了解決兼容問(wèn)題,我們采用 cross-env 解決跨平臺(tái)的問(wèn)題。

$ 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(), // 關(guān)閉 HMR 功能
    new webpack.NamedModulesPlugin()
  ],
  // ...
};

Tips
[chunkhash]不能和 HMR 一起使用,換句話說(shuō),不應(yīng)該在開發(fā)環(huán)境中使用 [chunkhash] (或者 [hash]),這會(huì)導(dǎo)致許多問(wèn)題。詳情見 #2393#377。

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

多配置文件配置

有時(shí)我們會(huì)需要為不同的環(huán)境配置不同的配置文件,可以選擇 簡(jiǎn)易方法,這里我們采用較為先進(jìn)的方法。先準(zhǔn)備一個(gè)基本的配置文件,包含了所有環(huán)境都包含的配置,然后用 webpack-merge 將它和特定環(huán)境的配置文件合并并導(dǎo)出,這樣就減少了基本配置的重復(fù)。

$ 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') // 在編譯的代碼里設(shè)置了`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 命令行選項(xiàng)見 Command Line Interface。

代碼分離

入口分離

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

$ 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)用入口分離的代碼得到了兩個(gè)大文件,這是因?yàn)閮蓚€(gè)入口文件都引入了lodash,這很大程度上造成了冗余,在同一個(gè)頁(yè)面中我們只需要引入一個(gè)lodash就可以了。

抽取相同部分

我們使用 CommonsChunkPlugin 插件來(lái)將相同的部分提取出來(lái)放到一個(gè)單獨(dú)的模塊中。

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分離出來(lái)了。

動(dòng)態(tài)引入

我們還可以選擇以動(dòng)態(tài)引入的方式來(lái)實(shí)現(xiàn)代碼分離,借助 import() 實(shí)現(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ù)原來(lái)的內(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" */這段注釋,它并不是可有可無(wú)的,它能幫助我們結(jié)合output.chunkFilename把分離出的模塊最終命名為lodash.bundle.js而非[id].bundle.js

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

懶加載 (lazy loading)

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

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; // 引入模塊的默認(rèn)函數(shù)

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

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

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

// 熱加載部分注釋

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

構(gòu)建之,控制臺(tái)此時(shí)并無(wú)輸出,點(diǎn)擊按鈕,會(huì)看到控制臺(tái)如下輸出:

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"!

說(shuō)明 print 模塊只在我們點(diǎn)擊時(shí)才引入了,すっげえ!

緩存 (caching)

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

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

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)容相關(guān)的,只要內(nèi)容發(fā)生了改變,構(gòu)建后文件名的 hash 就會(huì)發(fā)生改變。

還有一個(gè)要點(diǎn)是提取出第三方庫(kù)放到單獨(dú)模塊中,因?yàn)樗鼈兪遣惶赡茴l繁發(fā)生改變的,所以無(wú)需多次加載這些模塊,提取的方法用 CommonsChunkPlugin 插件,這個(gè)插件上文中提到過(guò),指定入口文件名時(shí)它會(huì)提取改入口文件為單個(gè)文件,不指定則會(huì)提取 webpack 的運(yùn)行時(shí)代碼。

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: [ // 第三方庫(kù)可以統(tǒng)一放在這個(gè)入口一起合并
      '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 自身的運(yùn)行時(shí)代碼放在 runtime 模塊
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

Tips
包含 vendor 的 CommonsChunkPlugin 實(shí)例必須在包含 runtime 的之前,否則會(huì)報(bào)錯(cuò)。

src/index.js

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

// ...

如果我們?cè)?src 下新建一個(gè)文件h.js,再在index.js中引入它,保存,構(gòu)建之,我們發(fā)現(xiàn)有些沒(méi)改變的模塊的 hash 也發(fā)生了改變,這是因?yàn)榧尤?code>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(), // 替換掉原來(lái)的`module.id`
    new CleanWebpackPlugin(['dist']),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
    })
    // new webpack.HotModuleReplacementPlugin(),
    // new webpack.NamedModulesPlugin()
  ],
  // ...
};

再來(lái)執(zhí)行剛才那波操作,就會(huì)發(fā)現(xiàn)無(wú)關(guān)修改的模塊 hash 未變了。

Shimming

Tips
你可以將 shim 簡(jiǎn)單理解為是用于兼容 API 的小型庫(kù)。

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

$ 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({ // 設(shè)置全局變量
      $: '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,點(diǎn)擊頁(yè)面按鈕,成功了。

另外,如果你需要在某些模塊加載時(shí)設(shè)置該模塊的全局變量,請(qǐng)看 這里。

結(jié)尾的一點(diǎn)廢話

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

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

Reference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容