開始使用 Webpack 2
Webpack 2 的文檔完成 就會推出 beta 版本。如果你已經知道怎么去配置它,那么你無需等待文檔就可以使用 Webpack 2 了。
什么是 Webpack?
最初的時候,Webpack 僅是一個 JavaScript 的模塊打包工具。隨著 Webpack 日漸流行,逐漸演變成了前端代碼的管理工具(不論是人為故意還是社區推動的)。

以前的運行方式是:標記文件、樣式文件和 JavaScript 文件都被分割開的。你必須要獨立管理每一個文件,使得所有文件可以正確的運行。
像 Gulp 這樣的構建工具可以操作許多不同的預處理器和編譯器,不過基本上所有的工作都看成是把一個源文件作為輸入,經過處理后生成一個編譯后的輸出文件。Gulp 做的工作更像是一個任務接著一個任務的進行的,沒有從全局的管理上考慮。這就會加重開發者的負擔:在生產環境下,開發者需要知道任務在哪里結束,然后需要正確地把所有任務都組裝在一起。
Webpack 嘗試詢問一個大膽的問題來減輕開發者的負擔:是否有一個開發過程可以處理所引用到的依賴?我們是否可以簡單地用某種方式去寫代碼,而構建程序會去管理最終所必需使用到的代碼?

Webpack 的方式是:如果 Webpack 知道了它,它會把你實際用到的東西打包在構建產物里。
如果過去幾年你已經混跡在 web 社區里,你應該知道解決問題更好的方式是:用 JavaScript 構建。Webpack 可以通過 JavaScript 傳遞依賴讓構建過程更加容易。Webpack 的設計真正厲害之處并不在于代碼的管理,而是它的管理層 100% 都是由 JavaScript 寫的(Node 特性)。Webpack 使得你有能力在寫 JavaScript 時候對系統全局有更好的把握和掌控。
換句話說:你不需要為 Webpack 寫任何代碼,只需要給項目寫代碼,然后Webpack會自動運行(當然,一些配置文件是少不了的)
簡而言之,如果你已經與以下任意一個問題糾結過:
-
加載依賴項
-
在生產版本中包含了未使用的 CSS 或者 JS
-
意外多次加載同一個庫
-
遇到 CSS 和 JavaScript 作用域的問題
-
在 JavaScript 里使用一個例如 Node/Bower 這樣很好的管理系統,或者依賴一個可怕瘋狂的配置來正確使用那些模塊
-
需要更好地優化生產出資源,但又怕破壞到某些事情
那么,你可以從 Webpack 獲得非常多的幫助。Webpack 可以輕易地解決上面問題,因為它可以通過 JavaScript 管理你的依賴和加載順序,而不是通過你的開發者頭腦。最棒的部分?Webpack 甚至可以直接地運行在服務端,這意味著你可以使用 Webpack 構建 漸進加強 的網站。
第一步
在這篇指導里,我們會使用 Yarn ( brew install yarn ) 來替代 npm ,不過這個取決于你,它們都可以做到同樣的事情。在項目文件夾下,我們會在終端窗口里執行以下的指令,在全局和本地項目里添加 Webpack 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 配置文件 webpack.config.js :
'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 基本上會像以下這個流程進行工作:
-
從 context 文件夾開始
-
尋找 entry 上的文件名
-
讀取內容。每當遇到 import ( ES6 ) 或者 require() (Node) 依賴項時,它會解析這些代碼,并且打包到最終構建里。接著它會不斷遞歸搜索實際需要的依賴項,直到它到達了“樹”的底部。
- 從上一步接著,Webpack 把所有東西打包到 output.path 的文件夾里,并使用 output.filename 命名( [name] 表示使用 entry 項的 key)
所以我們的 src/app.js 文件看起來就像這樣(假設我們之前執行的是 yarn add --dev moment ):
'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"
執行
webpack -p
注意: _p_ 表示 “生產” 模式,這時候的輸出文件會被 uglifies/minifies。
它會輸出一個 dist/app.bundle.js 文件,同時在控制臺里打出當前日期時間的日志。注意,Webpack 會自動知道 'moment' 模塊是指向哪里(即使在目錄里,你有一個 moment.js 文件,默認情況下 Webpack 還是會優先使用 moment Node 模塊)。
多文件工作
你可以通過修改 entry 對象來指定任意數量的輸入或者輸出點。
多個文件打包在一起
'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 里。
多個文件,多個輸出
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.js 、 dist/events.bundle.js , 和 dist/contact.bundle.js 。
先進的自動打包
如果你將應用分開打包到多個 output 文件里(如果你的應用有非常多的 JS 不需要在前期加載,這樣做是非常有效的),這里面是有可能會出現冗余代碼的,因為 Webpack 是獨立解析每個文件的依賴的。幸運的是,Webpack 已經有個內置的 CommonsChunk 插件處理這個問題:
module.exports = {
// …
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
filename: "commons.js",
minChunks: 2,
}),
],
// …
};
現在,縱觀所有 output 文件,如果你有任何模塊需要加載 2 次或者更多次(由 minChunks 設置),這些模塊會被打包在 commons.js 里,那么你就可以讓它在客戶端里緩存起來。當然,這會導致產生一個額外的頭部請求,但是你可以阻止客戶端多次下載同一個庫。在許多場景下,會有一個速度的凈收益。
開發環境
Webpack 實際上有自己的開發服務器,因此無論你是想開發一個靜態網站還是做一個前端的原型,它都可以滿足你。如果想這樣開發,只需要添加一個 devServer 對象到 webpack.config.js 就可以:
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
},
};
在終端執行:
webpack-dev-server
現在你的服務器跑在 localhost:8080 。 注意 script 標簽里的 ` /assets_ 路徑由 output.publicPath ` 決定——你可以隨便命名(如果你需要一個 CDN,這就非常有用了)_。
你無需刷新瀏覽器,Webpack 會熱加載任何 JavaScript 的改變。但是, 任何對 **webpack.config.js** 文件的改變都需要重啟服務器才會生效。
全局調用方法
需要使用在全局作用域下的函數?只需要在 webpack.config.js 的 output.library 進行簡單的設置:
module.exports = {
output: {
library: 'myClassName',
}
};
它會把你的打包文件捆綁在 window.myClassName 實例上。當設置了聲明作用域時,你可以在入口處調用這個方法(更多的設置可在 文檔 中查詢)。
Loaders
直到現在,我們僅僅處理了 JavaScript 文件。從 JavaScript 開始是很重要的,因為 JavaScript 是 Webpack 唯一識別的語言。但實際上,只要是用 JavaScript 傳遞的文件,我們都可以使用 Loaders 來處理任何種類的文件。
loader 可以是像 Sass 這樣的預處理器,也可以是像 Babel 這樣的編譯器。在 NPM 里,它們通常被命名為 *-loader ,例如: sass-loader 或者 babel-loader 。
Babel + ES6
如果你想在項目里通過 Babel 使用 ES6,我們首先需要本地安裝合適的 loaders。
yarn add --dev babel-loader babel-core babel-preset-es2015
然后把它添加到 webpack.config.js ,以便于讓 Webpack 知道在什么地方使用它。
module.exports = {
// …
module: {
rules: [
{
test: /\.js$/,
use: [{
loader: "babel-loader",
options: { presets: ["es2015"] }
}],
},
// Loaders for other file types can go here
],
},
// …
};
Webpack 的老用戶需要注意一點:Loaders 的核心理念還是保持一致的,但是它的語法有所改善。直到文檔完成了,我們才能知道準確合適的語法。
/\.js$/ 這條正則表達式會去搜索以 .js 后綴結束的文件,并通過 Babel 加載。Webpack 使用正則表達式來讓你可以完整的控制所有文件,而無需限定在某一個文件擴展名,又或者假定你是使用某種方式進行文件組織的。
CSS + Style Loader
如果我們只想加載應用所需的 CSS,我們同樣可以利用 Webpack 做到。如果我們直接在 index.js 文件里引入 CSS:
import styles from './assets/stylesheets/application.css';
我們會遇到以下的錯誤: You may need an appropriate loader to handle this file type 。記住,Webpack 只能夠理解 JavaScript,所以我們需要安裝合適的 loader:
yarn add --dev css-loader style-loader
然后給 webpack.config.js 添加一條規則:
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
// …
],
},
};
Loaders 會根據數組的逆序運行,也就是說 css-loader 會跑在 style-loader 前面。
你可能會注意到,在生產構建下,CSS 是會被打包到 JavaScript 里的, style-loader 會把你的樣式寫在 Style 標簽里。剛開始看起來是有點奇怪,但是隨著深入思考你會覺的這是有意義的。因為你可以在一些網絡連接上節省一個頭部請求的時間,并且只要你用了 JavaScript 來加載 Dom 節點,這可以有效地減少它自身的 FOUC 。
你也會發現 Webpack 黑盒的構建產物已經自動地通過將文件打包在一起,把所有的 @import 查詢都解析了(而不是依靠 CSS 默認會導致浪費頭部請求和加載資源非常慢的 import 功能)。
從 JS 里加載 CSS 是非常神奇的, 因為這樣你可以用新的方式將 CSS 模塊化。 也就是說你可以通過 button.js 加載 button.css ,而如果 button.js 實際上沒有用到,對應的 CSS 也不會被調價到生產構建中。如果你是堅持使用面向組件 CSS 實踐方法的,例如:SMACSS 或者 BEM,你會體會到將 CSS 和 標記語言 + JavaScript 更加緊密聯系的價值。
CSS + Node 模塊
我們可以使用 Webpack 里的 ~ 前綴來引入 Node 模塊。假如我們執行了 yarn add normalize.css ,那么就可以這么用:
@import "~normalize.css";
這樣可以充分利用 NPM 管理第三方樣式文件的版本,還可以避免了我們進行復制黏貼。更長遠地看,用 Webpack 打包 CSS 相比使用 CSS 默認的 import 有明顯的優勢,這是因為它可以為客戶端消除頭部請求以及緩慢的加載時間。
更新:本章節和下一章節為了解答對通過 CSS 模塊來簡單引入 Node 模塊的疑惑,以及提高文章的準確性進行了更新。感謝 Albert Fernández 的幫助。
CSS 模塊
你可能已經聽說過 CSS 模塊 ,當你使用 JavaScript 構建 Dom 節點,它可以不出意料地運行地非常好,它通過 JavaScript 加載,然后把你的 CSS 類神奇地設置好層疊作用域。如果你準備使用 CSS 模塊,可以用 css-loader 來打包( yarn add --dev css-loader ):
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
{ loader: "css-loader", options: { modules: true } }
],
},
// …
],
},
};
_注意:我們通過使用對象字面量的語法給 _css-loader_ 傳遞選項。你也可以用一個字符串作為簡寫來傳遞,這樣就會使用默認的選項,就像我們用 _style-loader_ 這樣。_
在用 Node 模塊引入 CSS 模塊的時候,需要注意的是你實際上可以拋棄 ~ 來直接引入。但是,當你 @import CSS 時,你可能會遇到一個構建錯誤。如果你得到是類似 "can’t find ..." 這樣的錯誤,你可以嘗試給 webpack.config.js 添加一個 resolve 對象,這樣會讓 Webpack 對預定的模塊順序有更好的理解。
const path = require("path");
module.exports = {
//…
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
};
我們規定了源文件目錄,這樣的做話 Webpack 可以解析地更加好。Webpack 會根據模塊名首先去尋找我們的源文件目錄,然后是安裝的 Node 模塊目錄里(也就是分別用 "src" 和 "node_modules" 來代替源文件目錄和 Node 模塊目錄)。
Sass
需要用 Sass?沒問題,可以這么安裝:
yarn add --dev sass-loader node-sass
然后加一條規則:
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 就是例子:
import styles from './assets/stylesheets/application.css';
安裝本地插件(我們需要 2016 年 10 月的 bata 版本)...
yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4
添加到 webpack.config.js :
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 ,你就可以在 output 目錄里發現 app.bundle.css 文件。接著就是非常輕松地像平常一樣在 HTML 里添加一個 tag 引入就可以了。
HTML
正如你想到的一樣,Webpack 也有一個叫 html-loader 的插件。但是當我們準備用 JavaScript 去加載 HTML 的時候,這里有一點需要注意,我無法通過一個單獨的例子就為你下一步做的事情準備好,因為這可能分出無數各種各樣地方法。通常來說,你可能是為了在 React 、 Angular 、 Vue ,又或者 Ember 這些大型框架里使用類似 JSX 、 Mustache ,或者 Handlebars 這樣的 JavaScript-flavored 標記語言。又或者你只是在使用一個 HTML 的預處理器,例如: Pug (Jade 的前身)或者 Haml 。再者你可能僅僅只是想按字面意思一樣把源文件里的 HTML 丟到構建目錄里。無論你想做什么,我都是無法假定。
結束語:你 可以 用 Webpack 加載標記語言文件,但是你需要決定自身的項目架構,這是 Webpack 和我都無法為你做這個決定的。不過根據上述例子作為參照,然后在 NPM 上搜索正確的 loaders,這些對于你來說應該是足夠用的了。
用模塊的方式思考
為了最大程度的使用 Webpack,你必須用模塊化、可復用性以及可獨立處理的思維方式去思考,讓每個模塊把各自負責的事情做好。這意味著類似下面這樣的文件:
└── js/
└── application.js // 300KB of spaghetti code
會變成這樣:
└── 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/
最后的結果出來的是非常簡潔,可復用的代碼。每個獨立的組件通過 import 來引入依賴,再通過 export 來暴露公共接口給其他模塊。把上面這些特性與 Babel 和 ES6 結合,你可以使用 JavaScript Classes 來實現更好的模塊化,而不需要考慮運行作用域。
來自:http://www.zcfy.cc/article/getting-started-with-webpack-2-thinking-in-code-2110.html