自我介紹環(huán)節(jié)
webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的模塊打包器(module bundler) 霞捡。當(dāng) webpack 處理應(yīng)用程序時(shí)榜揖,它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph)郎楼,其中包含應(yīng)用程序需要的每個(gè)模塊死嗦,然后將所有這些模塊打包成少量的 bundle(通常只有一個(gè)务甥,由瀏覽器加載)牡辽。
如上是webpack官網(wǎng)上的簡(jiǎn)介喳篇。從中我們可以看出webpack是一個(gè)模塊打包器,它獲取帶依賴的模塊并產(chǎn)生出與這些模塊相對(duì)于的靜態(tài)資源态辛。然而麸澜,自從發(fā)布后,webpack逐漸發(fā)展成為所有前端代碼的管理工具因妙。
為什么使用Webpack
分塊轉(zhuǎn)換的想法
模塊要能夠在客戶端中去執(zhí)行,則必須將它們通過(guò)請(qǐng)求從server端下載到browser端票髓。通常一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)模塊攀涵,只有需要的模塊會(huì)被轉(zhuǎn)換。當(dāng)所有資源都在一個(gè)模塊中時(shí)洽沟,不需要的模塊也會(huì)被轉(zhuǎn)換以故,這樣就顯得很浪費(fèi)資源和時(shí)間。將眾多的模塊切成許多片裆操,在初始化時(shí)的請(qǐng)求不會(huì)包括完整的代碼怒详,并且在初始化時(shí)不需要的模塊切片會(huì)在后續(xù)加載過(guò)程中按需加載。并且將模塊化的切片方式是可以由開(kāi)發(fā)人員自己定義的踪区。
常用的模塊系統(tǒng)解決方案:
一昆烁、<script>
標(biāo)簽類(lèi)型
最原汁原味的腳本引入方案,缺點(diǎn)如下(請(qǐng)不要問(wèn)我為什么不介紹優(yōu)點(diǎn)):
- 全局作用域下造成變量的沖突缎岗;
- 文件加載順序很重要静尼;
- 模塊與模塊之間的依賴要解決;
- 在大型項(xiàng)目中難以維護(hù)和管理传泊;
二鼠渺、CommonJs
該規(guī)范的核心思想是允許模塊通過(guò)require方法來(lái)同步加載所要依賴的其他模塊,然后通過(guò)exports
或module.exports
來(lái)導(dǎo)出需要暴露的接口眷细。
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
優(yōu)點(diǎn):
- 服務(wù)端模塊能夠重復(fù)利用拦盹;
- 有優(yōu)秀的包管理工具npm;
- 簡(jiǎn)單溪椎,上手容易普舆;
缺點(diǎn):
- 同步的模塊加載方式不適合在瀏覽器環(huán)境中,同步意味著阻塞加載校读,瀏覽器資源是異步加載的奔害;
- 不能非阻塞的并行加載多個(gè)模塊;
三地熄、AMD
由于瀏覽器端的模塊不能采用同步的方式加載华临,會(huì)影響后續(xù)模塊的加載執(zhí)行,因此AMD(Asynchronous Module Definition異步模塊定義)規(guī)范誕生了端考。
define("module", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });
require接口用來(lái)加載一系列模塊雅潭,define接口用來(lái)定義并暴露一個(gè)模塊揭厚。
優(yōu)點(diǎn):
- 適合瀏覽器的異步加載機(jī)制;
- 并行加載模塊扶供;
缺點(diǎn):
- 提高了開(kāi)發(fā)成本筛圆,代碼的閱讀和書(shū)寫(xiě)比較困難,模塊定義方式的語(yǔ)義不順暢椿浓;
四太援、CMD
CMD(Common Module Definition)規(guī)范和AMD很相似,盡量保持簡(jiǎn)單扳碍,并與CommonJS和Node.js的 Modules 規(guī)范保持了很大的兼容性提岔。在CMD規(guī)范中,一個(gè)模塊就是一個(gè)文件笋敞。
define(function(require, exports, module) {
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})
優(yōu)點(diǎn):
- 依賴就近碱蒙,延遲執(zhí)行;
- 可以很容易在 Node.js 中運(yùn)行夯巷;
缺點(diǎn):
- 依賴 SPM 打包赛惩,模塊的加載邏輯偏重;
五趁餐、ES6
EcmaScript6標(biāo)準(zhǔn)增加了JavaScript語(yǔ)言層面的模塊體系定義喷兼。在 ES6 中,我們使用export關(guān)鍵字來(lái)導(dǎo)出模塊后雷,使用import關(guān)鍵字引用模塊褒搔。需要說(shuō)明的是,ES6的這套標(biāo)準(zhǔn)和目前的標(biāo)準(zhǔn)沒(méi)有直接關(guān)系喷面,目前也很少有JS引擎能直接支持星瘾。
import "jquery";
export function doStuff() {}
module "localModule" {}
優(yōu)點(diǎn):
- 未來(lái)的ES規(guī)范;
缺點(diǎn):
- 瀏覽器對(duì)ES6的支持還需要一段時(shí)間惧辈;
- 能夠依賴的現(xiàn)有的模塊少琳状;
webpack的目標(biāo)如下:
- 拆分依賴樹(shù)成塊并按需加載;
- 讓初始化加載時(shí)間更少盒齿;
- 每一個(gè)靜態(tài)資源應(yīng)該是一個(gè)模塊念逞;
- 能夠集成第三方類(lèi)庫(kù);
- 適用于大型項(xiàng)目边翁;
- 能夠定制模塊打包的每一個(gè)部分翎承;
webpack較之其他類(lèi)似工具有什么不同?
- 有同步和異步兩種不同的加載方式符匾;
- 加載器(Loaders)可以將其他資源整合到JS文件中叨咖;
- 支持 CommonJs AMD 規(guī)范有豐富的開(kāi)源插件庫(kù),可以根據(jù)自己的需求自定義webpack的配置
眾所周知作為一個(gè)SEO的小TIPS,瀏覽器加載的資源越少甸各,響應(yīng)的速度也就越快垛贤,所以有時(shí)候我們通常會(huì)盡可能的將資源合并到一個(gè)主文件app.js里面。但有時(shí)候也會(huì)適得其反:當(dāng)項(xiàng)目十分龐大的時(shí)候趣倾,不同的頁(yè)面不能做到按需加載聘惦,而是將所有的資源一并加載,耗費(fèi)時(shí)間長(zhǎng)儒恋,性能降低善绎。會(huì)導(dǎo)致依賴庫(kù)之間關(guān)系的混亂,特別是大型項(xiàng)目時(shí)诫尽,會(huì)變得難以維護(hù)和跟蹤禀酱。
雖然webpack也會(huì)將所有資源放在一個(gè)文件里面,但是webpack可以很好的解決以上缺點(diǎn)箱锐,因?yàn)樗且粋€(gè)十分聰明的模塊打包系統(tǒng)比勉,當(dāng)你正確配置后劳较,它會(huì)比你想象中的更強(qiáng)大驹止,更優(yōu)秀。
核心概念
入口(Entry)
webpack 創(chuàng)建應(yīng)用程序所有依賴關(guān)系圖的起點(diǎn)观蜗。入口告訴 webpack 從哪里開(kāi)始臊恋,并根據(jù)依賴關(guān)系圖確定需要打包的內(nèi)容∧鼓恚可以將應(yīng)用程序的入口認(rèn)為是根上下文(contextual root) 或 app 第一個(gè)啟動(dòng)文件抖仅。參見(jiàn)如下示例:
// 單入口
module.exports = {
entry: {
main: './src/main.js'
}
}
// 多入口
module.exports = {
entry: {
app: ["./home.js", "./events.js"]
}
}
// 多入口,app(應(yīng)用主入口)砖第,vendors(公共庫(kù))
module.exports = {
entry: {
app: './src/main.js',
vendors: './src/vendors.js'
}
}
出口(Output)
將所有的資源(assets)歸攏在一起后撤卢,還需要告訴 webpack 在哪里打包應(yīng)用程序。webpack 的 output 屬性描述了如何處理歸攏在一起的代碼(bundled code)梧兼。
const path = require('path');
module.exports = {
output: {
// 打包輸出的目錄放吩,這里是絕對(duì)路徑,必選設(shè)置項(xiàng)
path: path.resolve(__dirname, './dist'),
// 資源基礎(chǔ)路徑
publicPath: '/dist/',
// 打包輸出的文件名羽杰,也可以設(shè)置為[name].js渡紫,動(dòng)態(tài)對(duì)應(yīng)入口的定義
filename: 'my-first-bundle.js'
}
}
加載器(Loader)
webpack 會(huì)把所有引用到的資源(.css, .html, .scss, .jpg, etc.) 都作為模塊處理。然而 webpack 自身只理解 JavaScript考赛。webpack loader 在文件被添加到依賴圖中時(shí)惕澎,將其轉(zhuǎn)換為模塊。在 webpack 的配置中 loader 有兩個(gè)目標(biāo):
- 識(shí)別出(identify)應(yīng)該被對(duì)應(yīng)的 loader 進(jìn)行轉(zhuǎn)換(transform)的那些文件颜骤;
- 轉(zhuǎn)換這些文件(test 屬性)唧喉,從而使其最終添加到 bundle 中(use 屬性);
示例如下:
module.exports = {
module: {
rules: [
{
// 這是個(gè)正則表達(dá)式
test: /\.jsx$/,
// 指定loader
loader: 'babel-loader'
},
{
test: /\.css$/,
// 指定多個(gè)loader
use: [
'css-loader',
'style-loader'
]
}
]
}
}
插件(Plugins)
loader僅在每個(gè)文件的基礎(chǔ)上執(zhí)行轉(zhuǎn)換,而插件(plugins) 更常用于在打包模塊的 生命周期執(zhí)行操作和自定義功能欣喧。webpack 的插件系統(tǒng)極其強(qiáng)大和可定制化腌零。想要使用一個(gè)插件,你只需要 require() 它唆阿,然后把它添加到 plugins 數(shù)組中益涧。多數(shù)插件可以通過(guò)選項(xiàng)(option)自定義。你也可以在一個(gè)配置文件中因?yàn)椴煌康亩啻问褂猛粋€(gè)插件驯鳖,這時(shí)需要通過(guò)使用 new 來(lái)創(chuàng)建它的一個(gè)實(shí)例闲询。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
// 應(yīng)用插件
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作為單獨(dú)的模塊
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: {
// 自動(dòng)解析確定的擴(kuò)展
extensions: ['.js', '.jsx'],
// 告訴 webpack 解析模塊時(shí)應(yīng)該搜索的目錄
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
alias: {
'src': path.resolve(__dirname, './src')
}
},
output: {
// 打包輸出的目錄,這里是絕對(duì)路徑浅辙,必選設(shè)置項(xiàng)
path: path.resolve(__dirname, './dist'),
// 資源基礎(chǔ)路徑
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 })
]
}