自我介紹環(huán)節(jié)
webpack 是一個現(xiàn)代 JavaScript 應用程序的模塊打包器(module bundler) 。當 webpack 處理應用程序時,它會遞歸地構(gòu)建一個依賴關系圖(dependency graph),其中包含應用程序需要的每個模塊,然后將所有這些模塊打包成少量的 bundle(通常只有一個,由瀏覽器加載)。

如上是webpack官網(wǎng)上的簡介。從中我們可以看出webpack是一個模塊打包器,它獲取帶依賴的模塊并產(chǎn)生出與這些模塊相對于的靜態(tài)資源。然而,自從發(fā)布后,webpack逐漸發(fā)展成為所有前端代碼的管理工具。
為什么使用Webpack
分塊轉(zhuǎn)換的想法
模塊要能夠在客戶端中去執(zhí)行,則必須將它們通過請求從server端下載到browser端。通常一個請求對應一個模塊,只有需要的模塊會被轉(zhuǎn)換。當所有資源都在一個模塊中時,不需要的模塊也會被轉(zhuǎn)換,這樣就顯得很浪費資源和時間。將眾多的模塊切成許多片,在初始化時的請求不會包括完整的代碼,并且在初始化時不需要的模塊切片會在后續(xù)加載過程中按需加載。并且將模塊化的切片方式是可以由開發(fā)人員自己定義的。
常用的模塊系統(tǒng)解決方案:
一、<script>標簽類型
最原汁原味的腳本引入方案,缺點如下(請不要問我為什么不介紹優(yōu)點):
- 全局作用域下造成變量的沖突;
- 文件加載順序很重要;
- 模塊與模塊之間的依賴要解決;
- 在大型項目中難以維護和管理;
二、CommonJs
該規(guī)范的核心思想是允許模塊通過require方法來同步加載所要依賴的其他模塊,然后通過exports或module.exports來導出需要暴露的接口。
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
優(yōu)點:
- 服務端模塊能夠重復利用;
- 有優(yōu)秀的包管理工具npm;
- 簡單,上手容易;
缺點:
- 同步的模塊加載方式不適合在瀏覽器環(huán)境中,同步意味著阻塞加載,瀏覽器資源是異步加載的;
- 不能非阻塞的并行加載多個模塊;
三、AMD
由于瀏覽器端的模塊不能采用同步的方式加載,會影響后續(xù)模塊的加載執(zhí)行,因此AMD(Asynchronous Module Definition異步模塊定義)規(guī)范誕生了。
define("module", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });
require接口用來加載一系列模塊,define接口用來定義并暴露一個模塊。
優(yōu)點:
- 適合瀏覽器的異步加載機制;
- 并行加載模塊;
缺點:
- 提高了開發(fā)成本,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢;
四、CMD
CMD(Common Module Definition)規(guī)范和AMD很相似,盡量保持簡單,并與CommonJS和Node.js的 Modules 規(guī)范保持了很大的兼容性。在CMD規(guī)范中,一個模塊就是一個文件。
define(function(require, exports, module) {
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})
優(yōu)點:
- 依賴就近,延遲執(zhí)行;
- 可以很容易在 Node.js 中運行;
缺點:
- 依賴 SPM 打包,模塊的加載邏輯偏重;
五、ES6
EcmaScript6標準增加了JavaScript語言層面的模塊體系定義。在 ES6 中,我們使用export關鍵字來導出模塊,使用import關鍵字引用模塊。需要說明的是,ES6的這套標準和目前的標準沒有直接關系,目前也很少有JS引擎能直接支持。
import "jquery";
export function doStuff() {}
module "localModule" {}
優(yōu)點:
- 未來的ES規(guī)范;
缺點:
- 瀏覽器對ES6的支持還需要一段時間;
- 能夠依賴的現(xiàn)有的模塊少;
webpack的目標如下:
- 拆分依賴樹成塊并按需加載;
- 讓初始化加載時間更少;
- 每一個靜態(tài)資源應該是一個模塊;
- 能夠集成第三方類庫;
- 適用于大型項目;
- 能夠定制模塊打包的每一個部分;
webpack較之其他類似工具有什么不同?
- 有同步和異步兩種不同的加載方式;
- 加載器(Loaders)可以將其他資源整合到JS文件中;
- 支持 CommonJs AMD 規(guī)范有豐富的開源插件庫,可以根據(jù)自己的需求自定義webpack的配置
眾所周知作為一個SEO的小TIPS,瀏覽器加載的資源越少,響應的速度也就越快,所以有時候我們通常會盡可能的將資源合并到一個主文件app.js里面。但有時候也會適得其反:當項目十分龐大的時候,不同的頁面不能做到按需加載,而是將所有的資源一并加載,耗費時間長,性能降低。會導致依賴庫之間關系的混亂,特別是大型項目時,會變得難以維護和跟蹤。
雖然webpack也會將所有資源放在一個文件里面,但是webpack可以很好的解決以上缺點,因為它是一個十分聰明的模塊打包系統(tǒng),當你正確配置后,它會比你想象中的更強大,更優(yōu)秀。

核心概念
入口(Entry)
webpack 創(chuàng)建應用程序所有依賴關系圖的起點。入口告訴 webpack 從哪里開始,并根據(jù)依賴關系圖確定需要打包的內(nèi)容??梢詫贸绦虻?em>入口認為是根上下文(contextual root) 或 app 第一個啟動文件。參見如下示例:
// 單入口
module.exports = {
entry: {
main: './src/main.js'
}
}
// 多入口
module.exports = {
entry: {
app: ["./home.js", "./events.js"]
}
}
// 多入口,app(應用主入口),vendors(公共庫)
module.exports = {
entry: {
app: './src/main.js',
vendors: './src/vendors.js'
}
}
出口(Output)
將所有的資源(assets)歸攏在一起后,還需要告訴 webpack 在哪里打包應用程序。webpack 的 output 屬性描述了如何處理歸攏在一起的代碼(bundled code)。
const path = require('path');
module.exports = {
output: {
// 打包輸出的目錄,這里是絕對路徑,必選設置項
path: path.resolve(__dirname, './dist'),
// 資源基礎路徑
publicPath: '/dist/',
// 打包輸出的文件名,也可以設置為[name].js,動態(tài)對應入口的定義
filename: 'my-first-bundle.js'
}
}
加載器(Loader)
webpack 會把所有引用到的資源(.css, .html, .scss, .jpg, etc.) 都作為模塊處理。然而 webpack 自身只理解 JavaScript。webpack loader 在文件被添加到依賴圖中時,將其轉(zhuǎn)換為模塊。在 webpack 的配置中 loader 有兩個目標:
- 識別出(identify)應該被對應的 loader 進行轉(zhuǎn)換(transform)的那些文件;
- 轉(zhuǎn)換這些文件(test 屬性),從而使其最終添加到 bundle 中(use 屬性);
示例如下:
module.exports = {
module: {
rules: [
{
// 這是個正則表達式
test: /\.jsx$/,
// 指定loader
loader: 'babel-loader'
},
{
test: /\.css$/,
// 指定多個loader
use: [
'css-loader',
'style-loader'
]
}
]
}
}
插件(Plugins)
loader僅在每個文件的基礎上執(zhí)行轉(zhuǎn)換,而插件(plugins) 更常用于在打包模塊的 生命周期執(zhí)行操作和自定義功能。webpack 的插件系統(tǒng)極其強大和可定制化。想要使用一個插件,你只需要 require() 它,然后把它添加到 plugins 數(shù)組中。多數(shù)插件可以通過選項(option)自定義。你也可以在一個配置文件中因為不同目的而多次使用同一個插件,這時需要通過使用 new 來創(chuàng)建它的一個實例。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
// 應用插件
const path = require('path');
const config = {
main: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
// 壓縮JS
new webpack.optimize.UglifyJsPlugin(),
// 生成HTML
new HtmlWebpackPlugin({template: './src/index.html'}) ,
// 分離入口文件vendor作為單獨的模塊
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.js'
})
]
};
module.exports = config;
完整配置文件DEMO
**webpack.config.js**
var path = require('path'),
webpack = require('webpack'),
ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'eval',
entry: {
main: './src/main.js'
},
resolve: {
// 自動解析確定的擴展
extensions: ['.js', '.jsx'],
// 告訴 webpack 解析模塊時應該搜索的目錄
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
alias: {
'src': path.resolve(__dirname, './src')
}
},
output: {
// 打包輸出的目錄,這里是絕對路徑,必選設置項
path: path.resolve(__dirname, './dist'),
// 資源基礎路徑
publicPath: '/dist/',
// 打包輸出的文件名
filename: 'build.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
cacheDirectory: true
}
},
{
test: /\.css$/,
/*
use: [
'css-loader',
'style-loader'
]
*/
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader?minimize'
})
},
// 支持less
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: "css-loader?minimize" },
{ loader: "less-loader" }
]
})
},
{
// 處理圖片文件
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 7186, // inline base64 if <= 7K
name: 'static/images/[name].[ext]'
}
},
{
// 處理字體文件
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 7186, // inline base64 if <= 7K
name: 'static/fonts/[name].[ext]'
}
}
]
},
plugins: [
// https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new ExtractTextPlugin({ filename: 'static/css/app.css', allChunks: true })
]
}