什么是Webpack?
按照官方文檔的解釋,Webpack就是個模塊打包工具,將模塊及其依賴打包生成靜態資源。在Webpack的機制里,所有的資源都是模塊(js,css,圖片等),而且可以通過代碼分隔(Code Splitting)的方法異步加載,實現性能上的優化。
為什么要使用Webpack
1 與react一類模塊化開發的框架搭配著用比較好。
2 屬于配置型的構建工具,比較用容易上手,160行代碼可大致實現gulp400行才能實現的功能。
3 webpack使用內存來對構建內容進行緩存,構建過程中會比較快。
Webpack的配置

Webpack的配置主要為了這幾大項目:
- entry:js入口源文件
- output:生成文件
- module:進行字符串的處理
- resolve:文件路徑的指向
- plugins:插件,比loader更強大,能使用更多webpack的api
配置中常用的loader:
- 處理樣式,轉成css,如:less-loader, sass-loader
- 圖片處理,如: url-loader, file-loader。兩個都必須用上。否則超過大小限制的圖片無法生成到目標文件夾中
- 處理js,將es6或更高級的代碼轉成es5的代碼。如: babel-loader,babel-preset-es2015,babel-preset-react
- 將js模塊暴露到全局,使用expose-loader
常用Plugins介紹
- 代碼熱替換, HotModuleReplacementPlugin
- 生成html文件,HtmlWebpackPlugin
- 將css成生文件,而非內聯,ExtractTextPlugin
- 報錯但不退出webpack進程,NoErrorsPlugin
- 代碼丑化,UglifyJsPlugin,開發過程中不建議打開
- 多個 html共用一個js文件(chunk),可用CommonsChunkPlugin
- 清理文件夾,Clean
- 調用模塊的別名ProvidePlugin,例如想在js中用$,如果通過webpack加載,需要將$與jQuery對應起來
CommonJS 與 AMD 支持
Webpack 對 CommonJS 的 AMD 的語法做了兼容, 方便遷移代碼 不過實際上, 引用模塊的規則是依據 CommonJS 來的
require('lodash') // 從模塊目錄查找
require('./file') // 按相對路徑查找
AMD 語法中, 也要注意, 是按 CommonJS 的方案查找的
define (require, exports. module) ->
require('lodash') # commonjs 當中這樣是查找模塊的
require('./file')
重點解釋
Chunk的概念
chunk是使用Webpack過程中最重要的幾個概念之一。在Webpack打包機制中,編譯的文件包括entry(入口,可以是一個或者多個資源合并而成,由html通過script標簽引入)和chunk(被entry所依賴的額外的代碼塊,同樣可以包含一個或者多個文件)。從頁面加速的角度來講,我們應該盡可能將所有的js打包到一個bundle.js之中,但是總會有一些功能是使用過程中才會用到的。出于性能優化的需要,對于這部分資源我們可以做成按需加載,通過require.ensure方法實現
require.ensure([], function(require) {
var dialog = require('./components/dialog');
// todo ...
});
而固定的公用代碼則獨立打包到trunk之中。在Webpack的配置中,我們可以通過CommonsChunkPlugin插件對指定的chunks進行公共模塊的提取。我們指定好生成文件的名字,以及想抽取哪些入口js文件的公共代碼,webpack就會自動幫我們合并好
var chunks = Object.keys(entries);
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors', // 將公共模塊提取,生成名為`vendors`的chunk
chunks: chunks,
minChunks: chunks.length // 提取所有entry共同依賴的模塊
})
],
Loader
Loader就是資源轉換器。由于在webpack里,所有的資源都是模塊,不同資源都最終轉化成js去處理。針對不同形式的資源采用不同的Loader去編譯,這就是Loader的意義。Loader在使用之前必須先通過npm安裝,然后在config里面通過module配置才能使用。舉個例子:
module: {
loaders: [{
test: /\.(png|jpg|jpeg|gif)$/,
loader: 'url?limit=10000&name=images/[name].[ext]'
}]}
上述配置中,test的作用是正則匹配,匹配到png或jpg或gif結尾的文件就采用url-loader來做對應的編譯。由于loader都是默認以-loader后綴結尾的,所以可以省略后綴"-loader",直接寫成url。問號后面是參數,表示10000B以下的圖片直接壓縮成base64編碼,超過10000B的圖片輸出到"images/文件名.拓展名"。上面的配置也可以這樣寫:
loaders: [{
test: /\.(png|jpg|jpeg|gif)$/,
loader: 'url-loader',
query:{
limit:'10000',
name:'images/[name].[ext]'
}
}]
Plugin
插件的引入和loader差不多,只是插件是以對象的形式引入。像靜態資源路徑的替換這種功能就能通過插件來處理。比如公用模塊打包到chunk的插件:
var chunks = Object.keys(entries);
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendors', // 將公共模塊提取,生成名為`vendors`的chunk
chunks: chunks,
minChunks: chunks.length // 提取所有entry共同依賴的模塊
})],
搭建自己的構建集成環境
介紹完上面幾個概念,我們就可以進入動手搭建腳手架的階段了。
項目目錄
假設我們要搭建的demo項目的目錄結構是這樣的:
- webapp/ # webapp根目錄
+ node_modules # node_modules
- src/ # 開發目錄
- index # index模塊
+ images/ # webapp圖片資源目錄
index.html # 模板
index.js # 模塊entry
style.less # 樣式表
webpack.config.js # webpack配置文件
package.json # 項目依賴文件
config.js # 項目配置文件
README.md # 項目說明
配置過程
項目目錄確定之后,下面開始來著手配置webpack。
安裝webpack
首先,全局安裝webpack,至于怎么裝...額,就不侮辱大家智商了。
安裝依賴
這一步也非常簡單,根據項目需要用到的依賴 npm i xxx--save-dev,也可以在配置webpack.config.js的過程中根據需要安裝。
配置webpack
這一步基本是webpack配置的全部內容。由于webpack默認讀取根目錄下的webpack.config.js文件,所以我們需要在根目錄手動創建。看看我們的webpack.config.js配置文件:
1、首先,引入我們需要用到的npm模塊
var path = require('path'); //node 原生path模塊
var webpack = require('webpack'); // webpack
var glob = require('glob'); // glob模塊,用于讀取webpack入口目錄文件
var ExtractTextPlugin = require('extract-text-webpack-plugin'); //webpack插件
var HtmlWebpackPlugin = require('html-webpack-plugin'); //webpack插件
var OpenBrowserPlugin = require('open-browser-webpack-plugin');//webpack插件
var CleanPlugin = require('clean-webpack-plugin')//webpack插件,用于清除目錄文件
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;//處理trunk
2、讀取入口文件
按照之前約定的項目目錄,我們的webpack入口文件所在目錄為src/index/index.js。所以,先要用glob讀取模塊入口。在這里,我們把讀取目錄定義為一個函數:
var getEntry = function(){
var entry = {};
glob.sync('./src/**/*.js')
.forEach(function(name){
var start = name.indexOf('/src')+4,
end = name.length - 3;
var n = name.slice(start,end);
n = n.slice(0,n.lastIndexOf(/'));
});
return entry;
}
然后將配置封裝在module.exports,定義入口entry字段,entry可以為字符串、對象或者數組,對應單頁面和多頁面應用
...
module.exports = {
entry: getEntry(),
...
}
3、定義資源輸出
資源打包輸出的配置在output內,主要包括path、filename、chunkFilename以及publicPath。path是資源輸出路徑,filename是資源命名規則,chunkFilename是公共js打包后輸出的命名,publicPath是靜態資源的公共路徑,比如線上CDN地址等,開發環境可以不設置,這樣CSS中的相對路徑就不會包括publicPath。在output輸出的時候可以根據開發環境或者生產環境選擇不同的文件命名方法,因為一般來說,線上的資源都是要經過壓縮的。比如我們定義一個"prod"變量判斷當前編譯環境:
...
output: {
path: path.resolve(__dirname, prod ? "./dist" : "./build"),
filename: prod ? "js/[name].min.js" : "js/[name].js",
chunkFilename: 'js/[name].chunk.js',
publicPath: prod ? "http:cdn.mydomain.com" : ""
},
...
[name]的值是根據入口entry顯示的文件名。比如index.js這個入口文件,對應的output的[name]值就應該是“index”,當然,我們還可以根據需要使用[hash]、[id]這樣的值。
4. 定義resolve
為了方便開發,我們可以定義自己的別名,以便很快捷地引用不同的模塊,別名(alias)的定義是在resolve對象之中。比如,
resolve:
{
alias:{
xyz: "/absolute/path/to/file.js"
}
}
那么,當我們在代碼中require('xyz')的時候,實際上我們是引入'/absolute/path/to/file.js'這個文件。還可以配置extensions對象,使得開發過程中文件資源的處理可以忽略后綴。在我們的demo中,是這樣配置的:
...
resolve: {
//配置項,設置忽略js后綴
extensions: ['', '.js', '.less', '.css', '.png', '.jpg'],
root: './src',
// 模塊別名
alias: {}
},
...
5、配置loaders
loader的配置是在module中定義。根據文章開頭部分的介紹,loaders就是定義一個個資源處理器,demo項目主要用到下面幾個loader:
...
module: {
loaders: [{
test: /\.(png|jpg|jpeg|gif)$/,
loader: 'url?limit=10000&name=images/[name].[ext]'
}, {
test: /\.less$/,
loader: ExtractTextPlugin.extract('style', 'css!less')
}, {
test: /\.js[x]?$/, exclude: /node_modules/,
loader: 'babel?presets[]=es2015&presets[]=react'
}, {
test: /\.html$/,
loader: 'html?attrs=img:src img:srcset'
}] },
...
ExtractTextPlugin.extract是用來提取出單獨的CSS文件的插件,如果不使用這個插件處理樣式文件,CSS會內聯在頁面中,不利于做樣式表維護;而babel則是用來做es6轉換。
6、定義Plugins
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/index/index.html'
}),
new CleanPlugin(['dist', 'build']), // 啟動熱替換
new webpack.HotModuleReplacementPlugin(),
new ExtractTextPlugin('[name].css', {
allChunks: true
}),
new webpack.NoErrorsPlugin(),
new OpenBrowserPlugin({
url: 'http://localhost:8080'
}),
/* 公共*/
new CommonsChunkPlugin({
name: 'vendors',
minChunks: Infinity
}),
]
...
HtmlWebpackPlugin插件用來自動在頁面中注入chunk;HotModuleReplacementPlugin插件是用來做熱替換的,每次開發環境下的資源發生變更之后都會自動重新打包輸出,不需要重新構建;配置OpenBrowserPlugin插件可以在構建完成之后自動打開瀏覽器的"localhost:8080"這個路徑;CommonsChunkPlugin插件定義chunk名字,文章開始部分已做詳細介紹。
8、編譯環境判斷
在“步驟3”我們需要根據當前的編譯環境來選擇不同的資源輸出方式。編譯環境的判斷可以通過定義node的script來設置環境變量。在我們項目根目錄的package.json文件中,定義:
"scripts":
{
"dev": "webpack-dev-server",
"build": "webpack",
"deploy": "set NODE_ENV=production&&webpack -p --progress --colors" },
這樣的話,終端執行"npm run dev" 就相當于執行 "webpack-dev-server"。如果執行"npm run deploy",那就是編譯生產環境,node就會設置環境變量"NODE_ENV"為"production"。然后在webpack的配置文件中,通過"process.env.NODE_ENV"就可以讀取到"production"這個值。所以在配置的開頭,我們這樣定義一個局部變量:
var prod = process.env.NODE_ENV === 'production' ? true : false;
之后在配置文件的最后,根據當前的編譯環境,如果是生產環境就配置引用壓縮丑化插件"UglifyJsPlugin",如果是開發環境就配置webpack-dev-server
/ 判斷開發環境還是生產環境,添加uglify等插件 if (process.env.NODE_ENV === 'production') {
module.exports.plugins = (module.exports.plugins || [])
.concat([
new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
} }),
new webpack.optimize.OccurenceOrderPlugin(),
]);
} else {
module.exports.devtool = 'source-map';
module.exports.devServer = {
port: 8080,
contentBase: './build',
hot: true,
historyApiFallback: true,
publicPath: "",
stats: { colors: true },
plugins: [ new webpack.HotModuleReplacementPlugin() ]
};
}
模塊代碼
至此,我們已經完成了一個基本的webpack配置,接下來就是使用webpack構建代碼了。我們定義的index模塊的代碼入口是index.js,在Index.js內通過require()方式引入不同的資源用于打包:
require('./style.less'); //引入.less預處理文件
require('./index.html'); //引入同級目錄的index.html文件
然后,執行"npm run dev"即可查看本地環境的靜態資源效果。
demo項目的完整代碼可以查看github
參考鏈接
Webpack 入門指迷
webpack使用優化(基本篇)
阮一峰16個demo
Webpack 中文指南