一份關於webpack2和模塊打包的新手指南

webpack已成爲現代Web開發中最重要的工具之一。它是一個用於JavaScript的模塊打包工具,可是它也能夠轉換全部的前端資源,例如HTML和CSS,甚至是圖片。它可讓你更好地控制應用程序所產生的HTTP請求數量、容許你使用其餘資源的特性(例如Jade、Sass和ES6)。webpack還可讓你輕鬆地從npm下載包。css

本文主要針對那些剛接觸webpack的同窗,將介紹初始設置和配置、模塊、加載器、插件、代碼分割和熱模塊替換。html

在繼續學習下面的內容以前須要確保你的電腦中已經安裝了Node.js。前端

初始配置

使用npm初始化一個新項目並安裝webpack:node

mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack@beta --save-dev
mkdir src
touch index.html src/app.js webpack.config.js

編寫下面這些文件:python

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello webpack</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="dist/bundle.js"></script>
  </body>
</html>
// src/app.js
const root = document.querySelector('#root')
root.innerHTML = `<p>Hello webpack.</p>`
// webpack.config.js
const webpack = require('webpack')
const path = require('path')

const config = {
  context: path.resolve(__dirname, 'src'),
  entry: './app.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      include: path.resolve(__dirname, 'src'),
      use: [{
        loader: 'babel-loader',
        options: {
          presets: [
            ['es2015', { modules: false }]
          ]
        }
      }]
    }]
  }
}

module.exports = config

上面的配置是一個普通的出發點,它通知webpack將入口文件src/app.js編譯輸出到文件/dist/bundle.js中、使用babel將全部的.js文件從ES2015轉換成ES5。webpack

爲了讓它能夠轉換到ES5格式的JS文件,咱們須要安裝三個包:babel-core、webpack加載器babel-loader和預置babel-preset-es2015。使用{ modules: false }Tree Shaking 從打包文件中刪除未使用的導出項(exports)以減小文件大小。git

npm install babel-core babel-loader babel-preset-es2015 --save-dev

最後,用如下內容替換package.jsonscripts部分:github

"scripts": {
  "start": "webpack --watch",
  "build": "webpack -p"
},

在命令行中運行npm start將以監視模式啓動webpack,當src目錄中的.js文件更改時,它將從新編譯bundle.js。控制檯中的輸出展現了打包文件的信息,注意打包文件的數量和大小十分重要。web

打包信息

如今當你在瀏覽器中加載index.html頁面時會看到"Hello webpack."。npm

open index.html

打開dist/bundle.js文件看看webpack都作了哪些事情,頂部是webpack的模塊引導代碼,底部是咱們的模塊。到目前爲止,你可能對於這個尚未一個深入的印象。可是如今你能夠開始編寫ES6模塊,webpack將生成適用於全部瀏覽器的打包文件。

使用 Ctrl + C中止webpack,運行npm run build以生產模式編譯咱們的bundle.js

注意,打包文件的大小從2.61 kB減小到了585字節。再看一下dist/bundle.js文件,你會看到大量難懂的代碼,由於咱們使用UglifyJS壓縮了它。這樣的話,咱們可使用更少的代碼達到與以前同樣的效果。

模塊

優秀的webpack知道如何使用各類格式的JavaScript模塊,最著名的兩個是:

  • ES2015 import語句

  • CommonJS require()語句

咱們能夠經過安裝、導入lodash來測試這些格式的模塊。

npm install lodash --save
// src/app.js
import {groupBy} from 'lodash/collection'

const people = [{
  manager: 'Jen',
  name: 'Bob'
}, {
  manager: 'Jen',
  name: 'Sue'
}, {
  manager: 'Bob',
  name: 'Shirley'
}, {
  manager: 'Bob',
  name: 'Terrence'
}]
const managerGroups = groupBy(people, 'manager')

const root = document.querySelector('#root')
root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`

運行npm start啓動webpack並刷新index.html,你能夠看到一個根據manager分組的數組。

讓咱們將這個數組轉移到它本身的模塊people.js中。

// src/people.js
const people = [{
  manager: 'Jen',
  name: 'Bob'
}, {
  manager: 'Jen',
  name: 'Sue'
}, {
  manager: 'Bob',
  name: 'Shirley'
}, {
  manager: 'Bob',
  name: 'Terrence'
}]

export default people

咱們能夠在app.js中使用相對路徑輕鬆的導入它。

// src/app.js
import {groupBy} from 'lodash/collection'
import people from './people'

const managerGroups = groupBy(people, 'manager')

const root = document.querySelector('#root')
root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`

注意:像lodash/collection這樣沒有相對路徑的導入是導入安裝在/node_modules的模塊,你本身的模塊須要一個相似./people的相對路徑,你能夠以此區分它們。

加載器

咱們已經介紹過,你能夠經過配置像babel-loader這樣的加載器來告訴webpack在遇到不一樣文件類型的導入時該怎麼作。你能夠將多個加載器組合在一塊兒,應用到不少文件轉換中。在JS中導入.sass文件是一個很是的例子。

Sass

這種轉換涉及三個單獨的加載器和node-sass庫:

npm install css-loader style-loader sass-loader node-sass --save-dev

在配置文件中爲.scss文件添加新規則。

// webpack.config.js
rules: [{
  test: /\.scss$/,
  use: [
    'style-loader',
    'css-loader',
    'sass-loader'
  ]
}, {
  // ...
}]

注意: 任什麼時候候更改webpack.config.js中的任意一個加載規則都須要使用Ctrl + Cnpm start從新啓動構建。

加載器序列以相反的順序進行處理:

  • sass-loader 將Sass轉換爲CSS。

  • css-loader 將CSS解析爲JavaScript並解析全部依賴。

  • style-loader將咱們的CSS輸出到文檔中的<style>標籤。

你能夠將它們看做函數調用,將一個加載器的輸出輸入到下一個加載器中。

styleLoader(cssLoader(sassLoader('source')))

添加一個sass源文件:

/* src/style.scss */
$bluegrey: #2B3A42;

pre {
  padding: 20px;
  background: $bluegrey;
  color: #dedede;
  text-shadow: 0 1px 1px rgba(#000, .5);
}

你如今能夠直接在JavaScript中導入Sass文件。

// src/app.js
import './style.scss'

// ...

從新加載index.html,你應該會看到一些樣式。

JS中的CSS

咱們剛剛在JavaScript中將一個sass文件做爲模塊導入了。

打開dist/bundle.js並搜索「pre {」。事實上,咱們的sass已被編譯成一串CSS,並做爲一個模塊保存在咱們的打包文件中。當咱們將這個模塊導入JavaScript中時,style-loader會將這個字符串輸入到嵌入的<style>標籤中。

我知道你在想什麼---爲何?

我不會在這裏過多的討論這個問題,但這裏有幾個值得了解的緣由:

  • 你想要包含在項目中的JavaScript組件可能依賴於其餘資源才能正常運行(HTML,CSS,圖片,SVG),若是這些資源均可以打包在一塊兒,那麼導入和使用將會更容易。

  • 消除死代碼:當你的代碼再也不導入JS組件時,CSS也將再也不被導入。生成的打包文件將只會包含執行某些操做的代碼。

  • CSS模塊:CSS的全局命名空間很難保證對CSS的更改不會產生任何反作用。CSS模塊經過將CSS默認設置爲本地命名空間、提供能夠在JavaScript中引用的惟一類名來更改這一模式。

  • 經過打包、分割代碼等巧妙的方式來減小HTTP請求的數量。

圖片

加載器的最後一個例子是使用url-loader處理圖片。

在標準的HTML文檔中,當瀏覽器遇到<img>標籤或具備background-image屬性的元素時將請求圖片。你可使用webpack將圖片存儲爲JavaScript字符串來對小圖片進行優化處理。這樣你就能夠預先加載它們,而且瀏覽器沒必要在之後爲其單獨發起請求。

npm install file-loader url-loader --save-dev

添加一個加載圖片的規則:

// webpack.config.js
rules: [{
  test: /\.(png|jpg)$/,
  use: [{
    loader: 'url-loader',
    options: { limit: 10000 } // 將大小小於10kb的圖片轉爲base64字符串
  }]
}, {
  // ...
}]

使用Ctrl + Cnpm start從新啓動構建。

運行下面的命令下載測試圖片:

curl https://raw.githubusercontent.com/sitepoint-editors/webpack-demo/master/src/code.png --output src/code.png

如今能夠在app.js的頂部導入圖片:

// src/app.js
import codeURL from './code.png'
const img = document.createElement('img')
img.src = codeURL
img.style.backgroundColor = "#2B3A42"
img.style.padding = "20px"
img.width = 32
document.body.appendChild(img)

// ...

這樣將引入一個圖片,其中src屬性包含圖片自己的數據URL

<img src="data:image/png;base64,iVBO..." style="background: #2B3A42; padding: 20px" width="32">

此外,因爲css-loader保障了使用url()引用的圖片也能夠經過url-loader運行,因此能夠直接在CSS中內聯它。

/* src/style.scss */
pre {
  background: $bluegrey url('code.png') no-repeat center center / 32px 32px;
}

編譯成這樣:

pre {
    background: #2b3a42 url("data:image/png;base64,iVBO...") no-repeat scroll center center / 32px 32px;
}

靜態資源模塊

你如今應該能夠明白加載器是如何創建各類資源之間的依賴關係的。
這其實也就是webpack主頁上的圖片所展現的那樣:

資源依賴關係

雖然JavaScript是入口點,可是webpack認爲其餘類型資源(如HTML,CSS和SVG)都有本身的依賴關係,因此這些類型的資源應該被視爲構建過程的一部分。

插件

咱們已經看到一個內置的webpack插件的例子,在npm run build腳本中調用的webpack -p命令就是使用webpack附帶的UglifyJsPlugin插件以生產模式壓縮打包文件。

加載器能夠對單個文件運行轉換,插件能夠運行在更大的代碼塊上。

公共代碼

commons-chunk-plugin是webpack附帶的另外一個核心插件,用於建立一個單獨的模塊,爲多個入口文件分享公共代碼。到目前爲止,咱們一直在使用單個入口文件和單個輸出打包文件。在許多實際場景中,你將受益於將其分解爲多個輸入和輸出文件。

若是你的應用程序有兩個不一樣的區域須要分享某個模塊,例如:用於面向公共應用程序的app.js、用於管理區域的admin.js,你能夠像這樣爲其建立單獨的入口點:

// webpack.config.js
const webpack = require('webpack')
const path = require('path')

const extractCommons = new webpack.optimize.CommonsChunkPlugin({
  name: 'commons',
  filename: 'commons.js'
})

const config = {
  context: path.resolve(__dirname, 'src'),
  entry: {
    app: './app.js',
    admin: './admin.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js'
  },
  module: {
    // ...
  },
  plugins: [
    extractCommons
  ]
}

module.exports = config

注意output.filename的變化,如今包含了[name],它會被替換爲塊名稱。所以咱們能夠從這個配置中獲得兩個輸出文件、也是咱們的兩個入口文件:app.bundle.jsadmin.bundle.js

commonschunk插件生成第三個文件commons.js,其中包含的是咱們入口文件須要的公共模塊。

// src/app.js
import './style.scss'
import {groupBy} from 'lodash/collection'
import people from './people'

const managerGroups = groupBy(people, 'manager')

const root = document.querySelector('#root')
root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
// src/admin.js
import people from './people'

const root = document.querySelector('#root')
root.innerHTML = `<p>There are ${people.length} people.</p>`

這些入口文件將輸出如下文件:

  • app.bundle.js包括stylelodash/collection模塊

  • admin.bundle.js不包含額外的模塊

  • commons.js包括咱們的people模塊

而後咱們能夠在兩個區域中引入共享模塊:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello webpack</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="dist/commons.js"></script>
    <script src="dist/app.bundle.js"></script>
  </body>
</html>
<!-- admin.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello webpack</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="dist/commons.js"></script>
    <script src="dist/admin.bundle.js"></script>
  </body>
</html>

在瀏覽器中加載index.htmladmin.html能夠看到它們自動的建立了通用模塊。

提取CSS

另外一個流行的插件是extract-text-webpack-plugin,可用於將模塊提取到本身的輸出文件中。

下面咱們將修改.scss規則來編譯Sass,加載CSS,而後將其提取到本身的CSS打包文件中,從而將其從JavaScript打包文件中刪除。

npm install extract-text-webpack-plugin@2.0.0-beta.4 --save-dev
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractCSS = new ExtractTextPlugin('[name].bundle.css')

const config = {
  // ...
  module: {
    rules: [{
      test: /\.scss$/,
      loader: extractCSS.extract(['css-loader','sass-loader'])
    }, {
      // ...
    }]
  },
  plugins: [
    extractCSS,
    // ...
  ]
}

從新啓動webpack,你應該看到一個新的包app.bundle.css,你能夠像往常同樣直接引用它。

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello webpack</title>
    <link rel="stylesheet" href="dist/app.bundle.css">
  </head>
  <body>
    <div id="root"></div>
    <script src="dist/commons.js"></script>
    <script src="dist/app.bundle.js"></script>
  </body>
</html>

刷新頁面以確認咱們的CSS已經被編譯而且從app.bundle.js移動到app.bundle.css中。成功!

代碼分割

咱們已經瞭解了分割代碼幾種方法:

  • 手動建立單獨的入口文件

  • 將共享代碼自動拆分紅公共塊

  • 使用extract-text-webpack-plugin從咱們的編譯包中提取出塊文件

另外一個分割代碼的方法是使用System.import require.ensure。經過在這些函數中封裝代碼塊,你能夠在運行時建立一個按需加載的模塊。這能夠顯著提升加載時間性能,由於在開始時不向客戶端發送全部內容。System.import使用模塊名稱做爲參數,並返回一個Promise。require.ensure須要一個依賴關係的列表,一個回調和一個可選的模塊的名稱。

若是你的應用中有一段依賴於應用其餘部分不須要的依賴,那最好把它分離成單獨的包。咱們經過添加一個名爲dashboard.js的新模塊來演示一下,這個模塊須要引入d3模塊。

npm install d3 --save
// src/dashboard.js
import * as d3 from 'd3'

console.log('Loaded!', d3)

export const draw = () => {
  console.log('Draw!')
}

app.js的底部導入dashboard.js

// ...

const routes = {
  dashboard: () => {
    System.import('./dashboard').then((dashboard) => {
      dashboard.draw()
    }).catch((err) => {
      console.log("Chunk loading failed")
    })
  }
}

// demo async loading with a timeout
setTimeout(routes.dashboard, 1000)

由於咱們添加了異步加載模塊,因此咱們須要在配置文件中使用一個output.publicPath屬性,以便讓webpack知道在哪裏獲取它們。

// webpack.config.js

const config = {
  // ...
  output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/dist/',
    filename: '[name].bundle.js'
  },
  // ...
}

從新啓動構建,你會看到一個神祕的新打包文件0.bundle.js

打包信息

webpack爲了提醒你,使用[big]來突出顯示較大的包,

這個0.bundle.js將根據須要使用JSONP請求獲取,所以直接從文件系統加載文件不會再加載它。咱們須要運行一個服務器,任何服務器均可以。

python -m SimpleHTTPServer 8001

打開http://localhost:8001/

加載後一秒鐘,你應該看到一個指向咱們動態生成的打包文件 /dist/0.bundle.js的GET請求和打印到控制檯的「Loaded!」。成功!

Webpack Dev Server

實時從新加載能夠經過在文件更改時自動刷新來真正改善開發人員體驗。只需安裝它,並使用webpack-dev-server啓動它,你就能夠進行體驗了。

npm install webpack-dev-server@2.2.0-rc.0 --save-dev

修改package.json中的start腳本。

"start": "webpack-dev-server --inline",

運行npm start啓動服務器而且在你的瀏覽器中打開http://localhost:8080/

嘗試更改src目錄下的任意文件,例如更改people.js中一個名稱或者style.scss中的一個樣式,你會切身感覺到這一好處。

熱模塊替換

若是你對實時從新加載只是印象深入,那麼熱模塊替換(HMR)將會讓你大吃一驚。如今是2017年,可能你在使用全局狀態開發單頁面應用程序。在開發過程當中,你會對組件進行不少小的改動,而後但願在的瀏覽器中真實的看到這些變化。手動刷新頁面或使用實時從新加載,你的全局狀態將會消失,你不得不從頭開始。熱加載的出現今後改變了這種狀況。

在開發人員理想的工做流程中,你能夠對模塊進行更改,並在運行時進行編譯和交換,而無需刷新瀏覽器(丟棄本地狀態)或接觸其餘模塊。雖然有時候仍然須要手動刷新,但HMR仍然能夠節省大量的時間,預計它在將來會很流行。

package.json中對start腳本進行最後一次編輯。

"start": "webpack-dev-server --inline --hot",

app.js的頂部告訴webpack接受該模塊的熱加載以及它的全部依賴。

if (module.hot) {
  module.hot.accept()
}

// ...

注意:由於僅在開發階段使用,webpack-dev-server -hotmodule.hot設置爲true, 當在生產模式下構建、module.hot設置爲false時,這些將被從打包文件中分離出來。

NamedModulesPlugin添加到webpack.config.js中的插件列表中以改善控制檯中的日誌記錄性能。

plugins: [
  new webpack.NamedModulesPlugin(),
  // ...
]

最後,在頁面中添加一個<input>元素,咱們能夠在輸入框中添加一些文本,以證實在咱們更改模塊的時候不會發生全頁刷新。

<body>
  <input />
  <div id="root"></div>
  ...

npm start重啓服務器來看看熱加載!

在輸入框中輸入「HMR規則」,而後在people.js中更更名稱,你會發如今不刷新頁面的狀況下發生了內容更新而且輸入框丟失輸入聚焦狀態。

這只是一個簡單的示例,可是但願你能意識到這是很是有用的。對於像React這樣基於組件的開發這更是十分有用的,你有不少「笨」組件須要與其狀態分離,組件能夠在不丟失狀態的狀況下被更新並從新呈現,所以你能夠不斷的得到即時反饋。

熱加載CSS

更改style.scss中<pre>元素的背景顏色,你會發現它並無被HMR更新。

pre {
  background: red;
}

事實證實,當你使用style-loader時,CSS的HMR能夠直接使用而不須要作任何操做。咱們經過將CSS模塊提取到外部的沒法替代的CSS文件中來去除這個關聯。

若是咱們將Sass規則恢復到初始狀態,並從插件列表中刪除extractCSS,那麼你也能夠看到Sass的熱加載。

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

HTTP/2

使用像webpack這樣的模塊打包工具的好處主要是你能夠經過控制資源的構建方式來幫助你提升應用性能。多年來,將文件鏈接起來以減小客戶端上須要的請求數量一直被認爲是最佳實踐。但HTTP / 2如今容許在單個請求中傳送多個文件,所以鏈接文件再也不是具備極端有效性的解決方法,可是它仍然很重要。你的應用程序實際上也能夠從多個擁有單獨緩存的小文件中受益,客戶端能夠獲取單個更改的模塊,而沒必要再次請求存在大部分相同內容的整個包。

送給你的結尾語

我但願這個關於webpack2的介紹對你有所幫助、可以開始使用它來產生很好的效果。圍繞webpack的配置、加載器和插件的學習可能須要一些時間,可是瞭解這個工具的工做原理確定是頗有好處的。

原文地址:https://www.sitepoint.com/beg...

相關文章
相關標籤/搜索