Webpack
Webpack是時下最流行的模塊打包器
它的主要任務就是將各種格式的JavaScript代碼,甚至是靜態文件
進行分析、壓縮、合併、打包,最後生成瀏覽器支持的代碼
特點:
- 代碼拆分方案:webpack可以將應用代碼拆分成多個塊,每個塊包含一個或多個模塊,塊可以按需異步加載,極大提升大規模單頁應用的初始加載速度
- 智能的靜態分析:webpack的智能解析器幾乎可以處理任何第三方庫
- Loader加載器:webpack只能處理原生js模塊,但是loader可以將各種資源轉換爲js模塊
- plugin插件:webpack有功能豐富的插件系統,滿足各種開發需求
- 快速運行:webpack 使用異步 I/O 和多級緩存提高運行效率,使得它能夠快速增量編譯
不過它也有自己的缺點
配置過於複雜;如果使用不當(比如過度優化),會產生難以預料的問題
但是瑕不掩瑜,它仍是優秀的前端自動化構建工具
安裝
webpack是使用Node.js開發的工具,可通過npm(Node.js的包管理工具)進行安裝
我們可以去Node.js官網下載
我的電腦是Windows×64位操作系統,那麼我就點擊Windows Installer 64-bit下載
下載安裝完畢後我們可以打開命令提示符檢測一下
使用Win+R快捷鍵,輸入cmd打開命令行
輸入:node -v
顯示了node版本6.2
這就證明我們安裝成功了
準備
下面我們要做的是在全局安裝webpack
因爲大部分情況我們需要已命令行工具的形式使用webpack,所以安裝在全局更方便(-g)
輸入:npm install webpack -g
安裝完畢後我們需要手動構建項目文件
現在我在桌面上新建了一個項目文件夾demo
內部很簡單,一個網頁index.html和一個用來放資源的文件夾
下面我們還要配置package.json項目文件
一個很簡單的方式就是利用npm自動創建
現在將命令符引導到我們項目的根路徑
(如果下載了git,可以直接在桌面git bash)
輸入:npm init
(最好將webpack也作爲項目依賴安裝到項目中,而不限於全局,官方推薦,方便引用 npm install webpack
)
輸入這個命令後,會讓你輸入項目名稱、項目描述等等
由於我們不需要發佈,這些都不重要,所以我們直接回車默認就可以了
完畢後我們就會發現文件根目錄下多了一個文件
有了package.json,webpack才能管理好模塊之間的依賴關係
文件
下面做一個十分簡單的頁面
/* greet.css */
p {
font-size: 50px;
color: orangered;
}
css中我們爲p標籤添加樣式
/* greet.js */
require('../css/greet.css');
var p = document.createElement('p');
p.innerHTML = 'hello world!';
module.exports = p;
greet.js中引入模塊,創建p標籤
/*entry.js*/
var p = require('./greet');
document.appendChild(p);
入口文件中將p標籤添加到頁面
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="./out/index.js"></script>
</body>
</html>
html值只是簡單的引入腳本
不過此時它還不存在
它是我們要使用webpack將會生成的文件
上面的依賴關係用圖片來展示就是個這個樣子地
配置文件
僅僅做了這些還不夠
我們還有一個最重要的步驟沒有完成——編寫webpack配置文件
在項目根目錄下創建一個文件webpack.config.js
/* webpack.config.js */
module.exports = {
entry: './src/js/entry.js',
output: {
path: './out',
publicPath: './out',
filename: 'index.js'
},
module: {
loaders: [
{test: /\.js$/, loader: 'babel'},
{test: /\.css$/, loader: 'style!css'},
{test: /\.(jpg|png|gif|svg)$/, loader: 'url?limit=8192}
]
}
}
entry是項目的入口文件
output是構建項目輸出結果的描述,本身爲對象
- path:輸出目錄
- publicPath:輸出目錄對應外部路徑(從瀏覽器中訪問)
- filename:輸出文件名
publicPath其實是很重要的配置,它表示構建結果最終被真正訪問的路徑
但在我們的demo中,直接通過相對路徑訪問靜態資源,不涉及打包上線CDN,所以沒那麼重要
加載器
要想使用loaders就必須安裝
爲了讓我們不用在所有文件中都重複的引用加載器
我們還需要在webpack.config.js下的module中添加配置信息
形式參考上面的代碼
它有以下配置選線:
- test(必選):匹配loaders處理文件拓展名的正則表達式
- loader(必選):加載器名稱
- include/exclude(可選):手動添加必須處理的文件或屏蔽不需要處理的文件;
- query(可選):爲加載器提供額外設置選項
babel-loader
Babel是一種多用途的編譯器,通過它我們可以:
- 使用瀏覽器沒有完全支持的新標準(ES6、ES7)
- 使用基於JavaScript拓展的語言(React JSX、CoffeeScript…)
Babel是幾個模塊化的包,其核心功能位於babel-core的npm包中
每一個功能的拓展包,都需要我們單獨下載
比如解析Es6的babel-preset-es2015包
解析JSX的babel-preset-react包
我們可以在命令行中統一下載它們 npm install + 要下載的包(多個包用空格隔開)
輸入:npm install babel-core babel-loader babel-preset-es2015 babel-preset-react --save-dev
(我們的demo中可能用不到這些加載器,我是爲了演示)
注:–save命令可以把信息自動寫進package.json的dependencies字段
–save-dev可以寫入到devDependences中
(dependencies表示在生產環境中需要依賴的包,DevDependencies表示僅在開發和測試環節中需要依賴的包)
module: {
loaders:[
{
test: /\.js$/,
loader: 'babel', //loader: 'babel-loader'
query: {
presets: ['es2015','react']
}
},
...
]
}
注意我的註釋,‘-loader’是可以省略的
css-loader
css-loader使我們能夠利用@import 和 url(…)替代 require()
style-loader將計算後的樣式加入頁面中
二者組合在一起使我們能夠把css嵌入webpack打包後的js文件中(style標籤中)
在命令行中下載
輸入:npm install css-loader style-loader --save-dev
module: {
loaders:[
{
test: /\.css$/,
loader: 'style!css' //loader: 'style-loader!css-loader'
},
...
]
}
使用“!”可以加載並列的加載器
url-loader
webpack中一切都是模塊,包括JavaScript,包括css,也包括圖片等靜態資源
爲了將圖片資源變成模塊,我們需要url-loader(將圖片轉換爲base64),它還依賴file-loader
所以都需要下載
輸入:npm install url-loader file-loader --save-dev
module: {
loaders:[
{
test: /\.(jpg|png|gif|svg)$/,
loader: 'url?limit=8192' //loader: 'url-loader?limit=8192'
},
...
]
}
上面的limit=8192意思是不大於8KB(1024×8)的圖片纔會被打包處理爲base64的圖片
打包監聽
完成了配置文件
在命令行輸入webpack -w
可以打包我們的文件並且時刻監控我們的文件
(如果僅僅想打包就輸入webpack
命令)
一旦發生變化(Ctrl+S保存後),立刻重新打包
(ps:若想在命令行取消監聽,使用Ctrl+C)
出現了這些,表示打包成功了
回到我們的文件夾,我們發現out文件夾由webpack自動生成了
node-modules內部裝的就是我們剛剛下載的那些loaders
頁面中也成功顯示了hello world
插件系統
除了loader外,plugin插件是另一個擴展webpack能力的方式
與loader的處理資源內容的轉換不同,plugin功能範圍更廣泛
由於這是一篇入門指南的文章
簡單介紹一個插件,主要看這個流程
插件同樣需要安裝(內置的插件不需要額外安裝)和配置
extract-text-webpack-plugin
在我們的示例中,通過JavaScript加載了CSS是藉助style-loader的能力
(將CSS已style標籤的形式插入頁面,標籤內容通過JavaScript生成)
這種方法有很大弊端:樣式內容生效時間延後
換句話說,點開頁面的一瞬間,你會看到無樣式的頁面
雖然這時間很短,但是用戶體驗非常的差
一般情況下,我們都是會把link標籤插入head中,script放到body的最後面
這樣文檔被解析前,樣式就已經下載並解析
但現在樣式與JavaScript一起加載,所以造成這樣的效果
不過這個缺陷可以避免,那就是使用extract-text-webpack-plugin插件
由於它不是內置插件
所以我們首先利用命令行下載npm install extract-text-webpack-plugin
還要修改配置文件
/* webpakc.config.js */
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
...
plugins: [
new ExtractTextPlugin('[name].css')
]
}
添加plugins配置。值是一個數組,數組的每一項是一個plugin實例
重新打包監聽
我們發現它爲我們創建了main.css文件
然後在html中引用它
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="./out/main.css">
</head>
<body>
<script src="./out/index.js"></script>
</body>
</html>
這樣便不會出現短暫的無樣式瞬間了
構建本地服務器
我們之前都是利用 webpack -w
的watch模式進行實時構建打包監聽
除此之外,webpack還提供了webpack-dev-server來輔助開發與調試
(直接寫到內存而不是硬盤裏,更快的打包速度)
webpack-dev-server是一個基於Express框架的Node.js服務器
它提供一個客戶端運行環境,會被注入到頁面代碼中執行,並通過Socket.IO與服務器通信
於是服務器端的每次改動與重新構建都會通知頁面,頁面隨之可作出反應
使用它我們同樣需要安裝npm install webpack-dev-server
然後啓動即可webpack-dev-server
(它也擁有類似 webpack -w
的功能,輸入後打包監聽)
如果要配置本地服務器,那麼就在webpack.config.js中設置devserver屬性
devserver可配置選項如下:
- contentBase 設置本地服務器所在目錄(默認爲根文件夾)
- port 設置監聽端口,(默認8080)
- inline 若設置true,源文件改變時自動刷新頁面
- colors 若設置爲true,終端輸出文件爲彩色
- historyApiFallback 若設置爲true,所有跳轉將指向index.html(開發單頁應用時非常有用,依賴於H5 history API)
/* webpakc.config.js */
module.exports = {
...
output: {
path: './out/',
publicPath: 'http://localhost:8080/out/',
filename: '[name].js'
},
...
devServer: {
colors: true,
historyApiFallback: true,
inline: true
}
}
我們的html中也需要對路徑作出一些修改
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="http://localhost:8080/out/main.css">
</head>
<body>
<script src="http://localhost:8080/out/index.js"></script>
</body>
</html>
這樣我們可以在瀏覽器輸入網址localhost:8080
看到我們頁面了
不過這是因爲瀏覽器默認查找盤符下的index.html
如果我們的html文件名字不是index.html而是abc.html
那麼查看網頁就需要輸入localhost:8080/abc.html
調試
開發最討厭的就是找bug了
找bug就就需要調試
不過我們發現,一旦我們出錯了
瀏覽器顯示錯誤的位置都是webpack爲我們打包出的文件
這很不利於我們調試,所以我們需要Source Maps
用法很簡單,只需要在webpack.config.js添加devtool配置信息
/* webpakc.config.js */
module.exports = {
devtool: 'eval-source-map',
entry: ...
output: ...
}
devtool有以下配置選項
- source-map
在一個單獨的文件中產生一個完整且功能完全的文件(具有最好的source map),但是會減慢打包文件的構建速度 - cheap-module-source-map
在一個單獨的文件中生成一個不帶列映射的map,不帶列映射提高項目構建速度,但是也使得瀏覽器開發者工具只能對應到具體的行,不能對應到具體的列(符號),會對調試造成不便; - eval-source-map
使用eval打包源文件模塊,在同一個文件中生成乾淨的完整的source map。這個選項可以在不影響構建速度的前提下生成完整的source map,但是對打包後輸出的JS文件的執行具有性能和安全的隱患。不過在開發階段這是一個非常好的選項,但是在生產階段一定不要用這個選項 - cheap-module-eval-source-map
這是在打包文件時最快的生成source map的方法,生成的Source Map 會和打包後的JavaScript文件同行顯示,沒有列映射,和eval-source-map缺點類似
在我們的生產環境,最好的選項就是eval-source-map了
除此之外我們還可以利用命令行使用source map
webpack-dev-server --devtool sourcemap
指令
在開發中我們還比較常用的指令
--colors
部分信息高亮
--progress
顯示進度百分比信息
不同指令可以用空格隔開 webpack-dev-server --progress --colors --devtool sourcemap
不過每次都要敲這麼長的指令很麻煩
沒關係有辦法
找到package.json文件
在scripts下存儲我們的指令
/* package.json */
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"abc": "webpack-dev-server --progress --colors --devtool sourcemap" //增
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0"
...
}
}
這裏我起了一個名爲abc的指令
這樣在命令行中直接輸入npm run abc
就相當於使用了webpack-dev-server --progress --colors --devtool sourcemap
最後最後
我們來複習一下webpakc.config裏面的構造
/* webpakc.config.js */
var ExtractTextPlugin = require('extract-text-webpack-plugin'); /* 引用插件 */
module.exports = {
devtool: 'eval-source-map', /* source map 使調試更容易 */
entry: './src/js/entry.js', /* 主入口文件路徑 */
output: {
path: './out', /* 輸出文件路徑 */
publicPath: './out', /* 靜態資源完整路徑 */
filename: 'index.js' /* 輸出文件名 */
},
module: {
loaders: [ /* 加載器 */
{test: /\.js$/, loader: 'babel'},
{test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader','css-loader')},
{test: /\.(jpg|png|gif|svg)$/, loader:'url?limit=8192'}
]
},
plugins: [ /* 插件 */
new ExtractTextPlugin('[name].css')
],
devServer: { /* 本地服務器配置 */
colors: true,
historyApiFallback: true,
inline: true
}
}