Webpack2入門

webpack 2 將在其文檔完成以後正式發佈。但這並不意味着不能夠開始使用它,若是你知道怎麼配置的話。javascript

什麼是 Webpack?

簡單來講,Webpack 就是一個針對 JavaScript 代碼的模塊打包工具。可是自發布以來,它演變成了一個針對全部前端代碼的管理工具(不論是其自己有意仍是社區的意願)。css

舊的任務運行工具處理方式:HTML、CSS 和 JavaScript 都是分離的。必須分別對每一項進行管理,而且還要確保全部東西正確地部署到生產環境。html

像 Gulp 這樣的任務運行工具能夠操做不少不一樣的預處理器和編譯器,可是在全部狀況下,它都須要接收一個源碼輸入並將其處理成一個編譯好的輸出。然而,它是在不關心整個系統的狀況下逐個去處理的。這就是開發者的負擔了:檢查任務運行工具備無遺漏的地方,併爲全部改動的部分找到正確的方式,將它們在生產環境上協調一致。前端

Webpack 經過一個大膽的詢問試圖減輕開發者的負擔:若是開發過程的某個部分能夠本身管理依賴會怎麼樣?若是咱們能夠以這樣一種方式來簡單地寫代碼:構建過程僅基於最後所須要的東西來管理它本身,會怎麼樣?vue

Webpack 處理方式:若是是 Webpack 知道的代碼,那麼它就只會打包實際在生產環境當中使用的部分。java

若是你是過去幾年裏 Web 社區當中的一員,那麼你確定已經知道首選解決問題的方法:使用 JavaScript 構建。因此 Webpack 試圖經過用 JavaScript 傳遞依賴來使構建過程變得更加容易。可是它設計的精妙之處並不在於簡單的代碼管理部分,而在於它的管理層面是百分百有效的 JavaScript(還有 Node 特性)。Webpack 使你有機會寫出一些對總體系統有更好感知的 JavaScript 代碼。node

換句話說:你不是爲了 Webpack 寫代碼,而是爲了你的項目寫代碼。並且 Webpack 在保持進步(固然包括某些配置)。react

總而言之,若是你曾經掙扎於下面這些狀況中的其中之一:webpack

  • 不當心將一些不須要的樣式表或者 JS 庫引入生產環境,致使項目體積變大
  • 遇到做用域問題 - 不論是來自 CSS 仍是 JavaScript
  • 不停尋找一個好的系統好讓你能夠在 JavaScript 代碼裏使用 Node 或 Bower 的模塊,或者依賴一系列瘋狂的後端配置來正確地使用那些模塊
  • 須要優化資源分發機制卻又擔憂會破壞掉某些東西

…那麼你就能夠受益於 Webpack 了。它經過讓 JavaScript 取代開發者的大腦來關心依賴和加載順序,輕鬆地解決了上面這些問題。最好的部分是什麼?Webpack 甚至能夠在服務端無縫運行,這意味着你仍然可使用 Webpack 來構建漸進式加強的網站。nginx

第一步

在這篇教程裏咱們將使用 Yarnbrew install yarn)來替代 npm,但這徹底取決於你本身,它們作的是一樣的事情。打開到項目文件夾,在命令行窗口運行下面的命令添加 Webpack 2 到全局包和本地項目裏:

1
2
yarn global add webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10
yarn add --dev webpack@ 2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10

而後在根目錄新建一個 webpack.config.js 文件用來聲明 Webpack 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';
 
const webpack = require("webpack");
 
module.exports = {
context: __dirname + "/src",
entry: {
app: "./app.js",
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

注意:__dirname指的是根目錄。

還記得 Webpack 「知道」項目裏發生了什麼嗎?它是經過讀取你的代碼知道的(不用擔憂,它簽署了一份保密協議)。Webpack 基本作了下面這些事情:

  1. 從 context 對應的文件夾開始…
  2. …尋找 entry 裏全部的文件名…
  3. …而後讀取它們的內容。在解析代碼時,每個經過 import(ES6) 或 require()(Node) 引入的依賴都會被打包到最終的構建結果當中。它會接着搜索那些依賴,以及那些依賴的依賴,直到「依賴樹」的葉子節點 — 只打包它所須要的依賴,沒有其餘的東西。
  4. 接着,Webpack 將全部東西打包到 output.path 對應的文件夾裏,使用 output.filename 對應的命名模板來命名([name] 被 entry 裏的對象鍵值所替代)

因此若是 src/app.js 文件看起來像下面這樣的話(假設提早運行了 yarn add --dev moment):

1
2
3
4
5
'use strict';
import moment from 'moment';
var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log( rightNow );
// "October 23rd 2016, 9:30:24 pm"

接着運行:

1
webpack -p

注意:p 標記表示「生產(production)」模式而且會壓縮或醜化(uglify/minify)輸出。

而後它將輸出一個 dist/app.bundle.js 文件,做用就是打印出當前日期和時間到控制檯。注意到 Webpack 自動知道了 'moment' 指的是什麼(但若是已經有一個 moment.js 在你的目錄當中,那麼 Webpack 默認就會優先使用這個而不是 moment 的 Node 模塊)。

處理多個文件

你只須要經過修改 entry 對象就能夠指定任意數量所指望的 entry 或 output 點。

多個文件,打包在一塊兒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';
 
const webpack = require("webpack");
 
module.exports = {
context: __dirname + "/src",
entry: {
app: ["./home.js", "./events.js", "./vendor.js"],
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

全部文件會按數組順序一塊兒打包到 dist/app.bundle.js 一個文件當中。

多個文件,多個輸出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const webpack = require("webpack");
 
module.exports = {
context: __dirname + "/src",
entry: {
home: "./home.js",
events: "./events.js",
contact: "./contact.js",
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

另外,你還能夠選擇打包成多個 JS 文件來將應用拆解成幾個部分。像上面這樣作就能夠打包成三個文件:dist/home.bundle.jsdist/events.bundle.js 和 dist/contact.bundle.js

進階自動打包

若是你正在將應用拆解,打包成多個 output 的話(若是應用的某部分有大量不須要提早加載的 JS 的話,這樣作會頗有用),那麼在這些文件裏就有可能出現重複的代碼,由於在解決依賴問題的時候它們是互相不干預的。幸虧,Webpack 有一個內建插件 CommonsChunk 來處理這個問題:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
// …
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
filename: "commons.js",
minChunks: 2,
}),
],
// …
};

如今,在 output 的文件裏,若是有任意模塊加載了兩次或更多(經過 minChunks 設置該值),它就會被打包進一個叫 commons.js 的文件裏,後面你就能夠在客戶端緩存這個文件了。固然,這確定會形成一次額外的請求,可是卻避免了客戶端屢次下載相同庫的問題。因此在不少場景下,這都是提高速度的舉措。

開發

實際上 Webpack 有它本身的開發服務器,因此不管你正在開發一個靜態網站,或者只是正在原型化前端階段,這個服務器都是完美可用的。想要運行它,只須要在 webpack.config.js 裏添加一個 devServer 對象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
context: __dirname + "/src",
entry: {
app: "./app.js",
},
output: {
filename: "[name].bundle.js",
path: __dirname + "/dist/assets",
publicPath: "/assets", // New
},
devServer: {
contentBase: __dirname + "/src", // New
},
};

如今新建一個 src/index.html 文件,加入下面這行:

1
<script src="/assets/app.bundle.js"></script>

而後在命令行中,運行:

1
webpack-dev-server

服務器如今就運行在了 localhost:8080 上。注意 script 標籤中的 /assets 對應的是 output.publicPath 的值 - 能夠隨便填成你想要的命名(若是須要一個 CDN,這就頗有用了)。

當你更改 JavaScript 代碼的時候,Webpack 就會實時更新頁面而無需手動刷新瀏覽器。可是,任何對webpack.config.js 的更改都須要重啓服務器才能夠生效。

全局可訪問方法

須要在全局命名空間裏使用某些本身的方法嗎?只需簡單地在 webpack.config.js 裏設置 output.library

1
2
3
4
5
module.exports = {
output: {
library: 'myClassName',
}
};

…這樣就會把打包結果綁定到一個 window.myClassName 實例上。因此使用這種命名做用域,就能夠調用 entry 點裏面的方法了(能夠閱讀文檔獲取更多關於這個配置的信息)。

Loaders

目前爲止,咱們所講到的都是關於 JavaScript 代碼的使用。從 JavaScript 代碼開始是很是重要的,由於這是 Webpack 惟一使用的語言。咱們能夠處理任何文件類型,只要將它們傳進 JavaScript 代碼中。這個功能用 Loaders 來實現。

一個 loader 能夠指向一個像 Sass 的預處理器,或者像 Babel 的編譯器。在 NPM 中,它們一般是像 sass-loader 或babel-loader 這樣命名爲 *-loader

Babel + ES6

若是咱們想要在項目中經過 Babel 來使用 ES6,首先要在本地正確地安裝一些 loader:

1
yarn add --dev babel-loader babel-core babel-preset-es2015

…而後把它們添加進 webpack.config.js 好讓 Webpack 知道哪裏使用它們。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
// …
module: {
rules: [
{
test: /\.js$/,
use: [{
loader: "babel-loader",
options: { presets: [ "es2015"] }
}],
},
 
// Loaders for other file types can go here
],
},
// …
};

一個給 Webpack 1 用戶的提示:Loaders 的核心概念仍然是同樣的,但語法上作了改進。在他們完成文檔以前這可能不是確切的首選語法。

這樣作就能夠爲 /\.js$/ 正則表達式尋找以 .js 結尾的文件,最後經過 Babel 編譯加載。Webpack 依賴正則表達式給予你完整的控制 - 但它不會限制你的文件後綴,或者假設你的代碼必須以某種特定形式組織起來。舉個例子:也許你的/my_legacy_code/ 文件夾裏的代碼不是用 ES6 寫的,那麼你就能夠把上面的 test 修改成/^((?!my_legacy_code).)*\.js$/,這樣就能夠繞過這個文件夾,其他文件用 Babel 編譯。

CSS + Style Loader

若是咱們只想加載應用須要的 CSS,也能夠那麼作。假設有一個 index.js 文件,在裏面引入:

1
import styles from './assets/stylesheets/application.css';

就會獲得一個錯誤:You may need an appropriate loader to handle this file type。記住 Webpack 只能讀取 JavaScript,因此咱們必須安裝正確的 loader:

1
yarn add --dev css-loader style-loader

而後在 webpack.config.js 裏添加一個規則:

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [ "style-loader", "css-loader"],
},
// …
],
},
};

這些 loader 會以數組逆序運行。這意味着 css-loader 會在 style-loader 以前運行。

你可能注意到甚至在生產構建的結果中,也把 CSS 打包進了 JavaScript 裏面,而且 style-loader 手動地將樣式寫進了 <head> 中。乍一看這可能有點奇怪,但當你考慮足夠多的時候就會慢慢發現這實際上是有道理的。你保存了一個頭部請求(在某些鏈接上節省寶貴的時間),而且若是你用 JavaScript 來加載 DOM,這麼作基本上就消除了它自身的無樣式閃屏問題。

還注意到 Webpack 已經經過把全部文件打包成一個從而自動解決了全部的 @import 查詢問題(比起依賴 CSS 默認的引入致使沒必要要的頭部請求和緩慢的資源加載,這麼作顯然更好)。

從 JS 里加載 CSS 至關爽,由於你能夠用一種強有力的新方式去模塊化 CSS 代碼了。假設你只經過 button.js 加載了button.css,這就意味着若是 button.js 沒有實際用到的話,它的 CSS 也不會打包進咱們的生產構建結果。若是你堅持使用像 SMACSS 或者 BEM 那樣的面向組件的 CSS,就會知道把 CSS 和 HTML + JavaScript 代碼放更近的價值了。

CSS + Node modules

咱們能夠在 Webpack 裏用 Node 的 ~ 前綴去引入 Node Modules。假設咱們提早運行了 yarn add normalize.css,就能夠這麼用:

1
@ import "~normalize.css";

這樣就能夠全面使用 NPM 來管理第三方樣式庫(版本及其餘)而對咱們而言就無需複製粘貼了。更進一步的是,Webpack 打包 CSS 比使用默認的 CSS 引入有着顯而易見的優點,讓客戶端遠離沒必要要的頭部請求和緩慢的資源加載。

更新:這個部分和下面的部分爲了更準確都進行了更新,不用再困擾於使用 CSS Modules 去簡單地引入 Node Modules 了。感謝 Albert Fernández 的幫助!

CSS Modules

你可能已經據說過 CSS Modules,它將 CSS(Cascading Style Sheets)裏的 C(Cascading)給提出來了。它只在用 JavaScript 構建 DOM 的時候使用有最佳效果,但本質上來講,它巧妙地將 CSS 在加載它的 JavaScript 裏做用域化了(點擊這個連接學習更多相關知識)。若是你計劃使用它,CSS Modules 對應的 loader 是css-loaderyarn add --dev css-loader):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
{ loader: "css-loader", options: { modules: true } }
],
},
// …
],
},
};

注意:對於 css-loader 咱們使用了展開的對象語法來爲它添加配置。你能夠寫簡單的字符串表明使用默認配置,style-loader 就仍是這麼作的。


值得注意的是實際上在使用 CSS Modules 引入 Node Modules 的時候能夠去掉 ~ 符號(如@import "normalize.css";)。可是,當 @import 你本身的 CSS 時可能會遇到錯誤。若是你獲得了 「can’t find ___」 這樣的錯誤,嘗試添加一個 resolve 對象到 webpack.config.js 裏,好讓 Webpack 更好地理解你預期的模塊順序。

1
2
3
4
5
6
7
const path = require("path");
module.exports = {
//…
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
};

首先指定了咱們本身的源文件目錄,而後是 node_modules。這樣子 Webpack 解決起來就會處理得更好一些,按照那個順序先找咱們的源文件目錄,而後是已安裝的 Node Modules(分別用你本身的源碼和 Node Modules 目錄替換其中的src 和 node_modules)。

Sass

想用 Sass?沒問題,安裝:

1
yarn add --dev sass-loader node-sass

而後添加另外一條規則:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
// …
module: {
rules: [
{
test: /\.(sass|scss)$/,
use: [
"style-loader",
"css-loader",
"sass-loader",
]
}
// …
],
},
};

接下來當 JavaScript 調用 import 引入一個 .scss 或 .sass 文件時,Webpack 就會作它該作的事情了。

分開打包 CSS

或許你正在處理漸進式加強的網站,又或許由於其餘的緣由你須要一個分離的 CSS 文件。咱們能夠簡單地實現,只須要在配置裏用 extract-text-webpack-plugin 替換掉 style-loader,而無需改變其餘任何代碼。以 app.js 文件爲例:

1
import styles from './assets/stylesheets/application.css';

本地安裝插件(咱們須要這個的測試版本,2016年10月發佈):

1
yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4

添加到 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
loader: 'css-loader?importLoaders=1',
}),
},
 
// …
]
},
plugins: [
new ExtractTextPlugin({
filename: "[name].bundle.css",
allChunks: true,
}),
],
};

如今運行 webpack -p 的時候就能夠看到一個 app.bundle.css 文件出如今 output 目錄裏了。像往常同樣簡單地添加一個 <link> 標籤到 HTML 文件裏就能夠了。

HTML

你可能已經猜到,Webpack 還有一個 html-loader 插件。可是,當咱們開始用 JavaScript 加載 HTML 的時候,這實際上是一個能夠分支成不一樣方法的地方,並且我想不到一個單獨的簡單示例能夠覆蓋全部下一步操做的可能性。一般,你可能會在用 ReactAngularVue 或者 Ember 構建的大型系統中加載諸如 JSXMustache 或者 Handlebars 這樣偏向 JavaScript 的模板 HTML;或者你可能使用一個像 Pug(之前的 Jade)或者 Haml 這樣的 HTML 預處理器;或者你可能只是想簡單地將源文件目錄裏的 HTML 複製到構建結果目錄裏。無論你想作什麼,我沒辦法假設。

因此我準備在此結束本教程:你能夠用 Webpack 加載 HTML,但這一點你必須本身根據你的架構作出決策,不論是我仍是 Webpack 都沒辦法幫到你。不過使用上述例子做爲參考並在 NPM 上找到正確的 loader 應該足夠讓你繼續下去了。

從模塊角度思考

爲了最大程度發揮 Webpack 的做用,你不得不從模塊的角度去思考(小、可複用、自包含進程),一件件事情慢慢去作好。這意味着下面這樣的東西:

1
2
└── js/
└── application.js // 300KB of spaghetti code

把它變成:

1
2
3
4
5
6
7
8
9
10
11
12
└── js/
├── components/
│ ├── button.js
│ ├── calendar.js
│ ├── comment.js
│ ├── modal.js
│ ├── tab.js
│ ├── timer.js
│ ├── video.js
│ └── wysiwyg.js
└── application.js // ~ 1KB of code; imports from ./components/

結果是乾淨且可複用的代碼。每一個獨立的組件取決於導入自身的依賴,並按照它想要的方式導出到其餘模塊。配合 Babel + ES6 使用,還能夠利用 JavaScript Classes 作出更好的模塊化,而且不要去想它,做用域只是在起做用。

想知道更多關於模塊的內容,能夠看這篇 Preethi Kasreddy 寫的很棒的文章

相關文章
相關標籤/搜索