本文github倉(cāng)庫(kù)地址: https://github.com/Rynxiao/webpack-tutorial ,里面包括了本教程的所有代碼。
【如果你覺得這篇文章寫得不錯(cuò),麻煩給本倉(cāng)庫(kù)一顆星:-D】
1. 導(dǎo)語
1.1 什么叫做webpack
webpack is a module bundler.
webpack takes modules with dependencies and generates static assets representing those modules.
簡(jiǎn)單的概括就是:webpack是一個(gè)模塊打包工具,處理模塊之間的依賴同時(shí)生成對(duì)應(yīng)模塊的靜態(tài)資源。
1.2 webpack可以做一些什么事情
圖中已經(jīng)很清楚的反應(yīng)了幾個(gè)信息:
webpack把項(xiàng)目中所有的靜態(tài)文件都看作一個(gè)模塊
??熘g存在著一些列的依賴
多頁(yè)面的靜態(tài)資源生成(打包之后生成多個(gè)靜態(tài)文件,涉及到代碼拆分)
2. webpack安裝
全局安裝(供全局調(diào)用:如
webpack --config webpack.config.js
)npm install -g webpack
項(xiàng)目安裝
```javascript
npm install webpack
// 處理類似如下調(diào)用
import webpack from "webpack";
var webpack = require("webpack");
```
建議安裝淘寶的npm鏡像,這樣下載npm包會(huì)快上很多,具體做法:
// 方式一npm install xx --registry=https://registry.npm.taobao.org/// 方式二:安裝淘寶提供的npm工具npm install -g cnpm cnpm install xx// 方式三// 在用戶主目錄下,找到.npmrc文件,加上下面這段配置registry=https://registry.npm.taobao.org/
3. webpack的基本配置
創(chuàng)建配置文件(webpack.config.js
,執(zhí)行webpack命令的時(shí)候,默認(rèn)會(huì)執(zhí)行這個(gè)文件)
module.export = { entry : 'app.js', output : { path : 'assets/', filename : '[name].bundle.js' }, module : { loaders : [ // 使用babel-loader解析js或者jsx模塊 { test : /\.js|\.jsx$/, loader : 'babel' }, // 使用css-loader解析css模塊 { test : /\.css$/, loader : 'style!css' }, // or another way { test : /\.css$/, loader : ['style', 'css'] } ] }};
說明一: webpack.config.js
默認(rèn)輸出一個(gè)webpack
的配置文件,與CLI
方式調(diào)用相同,只是更加簡(jiǎn)便
說明二: 執(zhí)行webpack
命令即可以運(yùn)行配置,先決條件,全局安裝webpack
,項(xiàng)目安裝各模塊loader
說明三: entry
對(duì)應(yīng)需要打包的入口js
文件,output
對(duì)應(yīng)輸出的目錄以及文件名,module
中的loaders
對(duì)應(yīng)解析各個(gè)模塊時(shí)需要的加載器
一個(gè)簡(jiǎn)單的例子
basic/app.js
require('./app.css');document.getElementById('container').textContent = 'APP';
basic/app.css
* { margin: 0; padding: 0;}#container { margin: 50px auto; width: 50%; height: 200px; line-height: 200px; border-radius: 5px; box-shadow: 0 0 .5em #000; text-align: center; font-size: 40px; font-weight: bold;}
basic/webpack.config.js
/** * webpack打包配置文件 */module.exports = { // 如果你有多個(gè)入口js,需要打包在一個(gè)文件中,那么你可以這么寫 // entry : ['./app1.js', './app2.js'] entry : './app.js', output : { path : './assets/', filename : '[name].bundle.js' }, module : { loaders : [ { test : /\.js$/, loader : 'babel' }, { test : /\.css$/, loader : 'style!css' } ] }};
basic/index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>basic webpack</title></head><body> <div id="container"></div> <script src="./assets/main.bundle.js"></script></body></html>
在basic
文件夾執(zhí)行webpack
,打包信息如下
生成main.bundle.js
文件,chunk
名稱為main
,也是webpack
默認(rèn)生成的chunk
名
## 4. webapck常用到的各點(diǎn)拆分
### 4.1 entry相關(guān)
4.1.1webpack
的多入口配置
上例的簡(jiǎn)單配置中,只有一個(gè)入口文件,那么如果對(duì)應(yīng)于一個(gè)頁(yè)面需要加載多個(gè)打包文件或者多個(gè)頁(yè)面想同時(shí)引入對(duì)應(yīng)的打包文件的時(shí)候,應(yīng)該怎么做?
entry : { app1 : './app1.js', app2 : './app2.js'}
在multi-entry
文件夾執(zhí)行webpack
,打包信息如下
可見生成了兩個(gè)入口文件,以及各自對(duì)應(yīng)的chunk
名
### 4.2 output相關(guān)
4.2.1 output.publicPath
output: { path: "/home/proj/cdn/assets/[hash]", publicPath: "http://cdn.example.com/assets/[hash]/"}
引用一段官網(wǎng)的話:
The publicPath specifies the public URL address of the output files when referenced in a browser. For loaders that embed
<script>
or<link>
tags or reference assets like images, publicPath is used as the href or url() to the file when it’s different then their location on disk (as specified by path).
大致意思就是:publicPath
指定了你在瀏覽器中用什么地址來引用你的靜態(tài)文件,它會(huì)包括你的圖片、腳本以及樣式加載的地址,一般用于線上發(fā)布以及CDN部署的時(shí)候使用。
比如有下面一段配置:
var path = require('path');var HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry : './app.js', output : { path : './assets/', filename : '[name].bundle.js', publicPath : 'http://rynxiao.com/assets/' }, module : { loaders : [ { test : /\.js$/, loader : 'babel' }, { test : /\.css$/, loader : 'style!css' } ] }, plugins : [ new HtmlWebpackPlugin({ filename: './index-release.html', template: path.resolve('index.template'), inject: 'body' }) ]};
其中我將publicPath
設(shè)置成了http://rynxiao.com/assets/
,其中設(shè)置到了插件的一些東西,這點(diǎn)下面會(huì)講到,總之這個(gè)插件的作用是生成了上線發(fā)布時(shí)候的首頁(yè)文件,其中script
中引用的路徑將會(huì)被替換。如下圖:
4.2.2 output.chunkFilename
各個(gè)文件除了主模塊以外,還可能生成許多額外附加的塊,比如在模塊中采用代碼分割就會(huì)出現(xiàn)這樣的情況。其中chunkFilename
中包含以下的文件生成規(guī)則:
[id] 會(huì)被對(duì)應(yīng)塊的id替換.
[name] 會(huì)被對(duì)應(yīng)塊的name替換(或者被id替換,如果這個(gè)塊沒有name).
[hash] 會(huì)被文件hash替換.
[chunkhash] 會(huì)被塊文件hash替換.
例如,我在output中如下設(shè)置:
output : { path : './assets/', filename : '[name].[hash].bundle.js', chunkFilename: "chunk/[chunkhash].chunk.js"}
同時(shí)我修改了一下basic/app.js
中的文件
require('./app.css');require.ensure('./main.js', function(require) { require('./chunk.js');});document.getElementById("container").textContent = "APP";
其中對(duì)應(yīng)的chunk.js
就會(huì)生成帶有chunkhash
的chunk
文件,如下圖:
這在做給文件打版本號(hào)的時(shí)候特別有用,當(dāng)時(shí)如何進(jìn)行hash
替換,下面會(huì)講到
4.2.3 output.library
這個(gè)配置作為庫(kù)發(fā)布的時(shí)候會(huì)用到,配置的名字即為庫(kù)的名字,通??梢源钆?code style="margin: 1px 5px; padding: 0px 5px !important; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: "Courier New", sans-serif !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; border-radius: 3px !important;">libraryTarget進(jìn)行使用。例如我給basic/webpack.config.js
加上這樣的配置:
output : { // ... library : 'testLibrary' // ...}
那么實(shí)際上生成出來的main.bundle.js
中會(huì)默認(rèn)帶上以下代碼:
var testLibrary = (//....以前的打包生成的代碼);// 這樣在直接引入這個(gè)庫(kù)的時(shí)候,就可以直接使用`testLibrary`這個(gè)變量
4.2.4 output.libraryTarget
規(guī)定了以哪一種方式輸出你的庫(kù),比如:amd/cmd/或者直接變量,具體包括如下
"var"
- 以直接變量輸出(默認(rèn)library方式) var Library = xxx (default)
"this"
- 通過設(shè)置this
的屬性輸出 this["Library"] = xxx
"commonjs"
- 通過設(shè)置exports
的屬性輸出 exports["Library"] = xxx
"commonjs2"
- 通過設(shè)置module.exports
的屬性輸出 module.exports = xxx
"amd"
- 以amd方式輸出
"umd"
- 結(jié)合commonjs2/amd/root
例如我以umd
方式輸出,如圖:
### 4.3 module相關(guān)
4.3.1 loader
中!
代表的含義
require("!style!css!less!bootstrap/less/bootstrap.less");
// => the file "bootstrap.less" in the folder "less" in the "bootstrap"
// module (that is installed from github to "node_modules") is
// transformed by the "less-loader". The result is transformed by the
// "css-loader" and then by the "style-loader".
// If configuration has some transforms bound to the file, they will not be applied.
代表加載器的流式調(diào)用,例如:
{ test : /\.css|\.less$/, loader : 'style!css!less' }
就代表了先使用less加載器來解釋less文件,然后使用css加載器來解析less解析后的文件,依次類推
4.3.2 loaders
中的include
與exclude
include
表示必須要包含的文件或者目錄,而exclude
的表示需要排除的目錄
比如我們?cè)谂渲弥幸话阋懦?code style="margin: 1px 5px; padding: 0px 5px !important; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: "Courier New", sans-serif !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; border-radius: 3px !important;">node_modules目錄,就可以這樣寫
{ test : /\.js$/, loader : 'babel', exclude : nodeModuleDir }
官方建議:優(yōu)先采用include,并且include最好是文件目錄
4.3.3 module.noParse
使用了noParse
的模塊將不會(huì)被loaders
解析,所以當(dāng)我們使用的庫(kù)如果太大,并且其中不包含require
、define
或者類似的關(guān)鍵字的時(shí)候(因?yàn)檫@些模塊加載并不會(huì)被解析,所以就會(huì)報(bào)錯(cuò)),我們就可以使用這項(xiàng)配置來提升性能。
例如下面的例子:在basic/
目錄中新增no-parse.js
var cheerio = require('cheerio');module.exports = function() { console.log(cheerio);}
webpack.config.js
中新增如下配置:
module : { loaders : [ { test : /\.js$/, loader : 'babel' }, { test : /\.css$/, loader : 'style!css' } ], noParse : /no-parse.js/}
當(dāng)執(zhí)行打包后,在瀏覽器中打開index.html
時(shí),就會(huì)報(bào)錯(cuò)require is not defined
4.4 resolve相關(guān)
4.4.1 resolve.alias
為模塊設(shè)置別名,能夠讓開發(fā)者指定一些模塊的引用路徑。對(duì)一些經(jīng)常要被import或者require的庫(kù),如react,我們最好可以直接指定它們的位置,這樣webpack可以省下不少搜索硬盤的時(shí)間。
例如我們修改basic/app.js
中的相關(guān)內(nèi)容:
var moment = require("moment");document.getElementById("container").textContent = moment().locale('zh-cn').format('LLLL');
加載一個(gè)操作時(shí)間的類庫(kù),讓它顯示當(dāng)前的時(shí)間。使用webpack --profile --colors --display-modules
執(zhí)行配置文件,得到如下結(jié)果:
其中會(huì)發(fā)現(xiàn),打包總共生成了104個(gè)隱藏文件,其中一半的時(shí)間都在處理關(guān)于moment
類庫(kù)相關(guān)的事情,比如尋找moment
依賴的一些類庫(kù)等等。
在basic/webpack.config.js
加入如下配置,然后執(zhí)行配置文件
resolve : { alias : { moment : 'moment/min/moment-with-locales.min.js' }}
有沒有發(fā)現(xiàn)打包的時(shí)間已經(jīng)被大大縮短,并且也只產(chǎn)生了兩個(gè)隱藏文件。
配合module.noParse
使用
module.noParse
參看上面的解釋
noParse: [/moment-with-locales/]
執(zhí)行打包后,效果如下:
是不是發(fā)現(xiàn)打包的時(shí)間進(jìn)一步縮短了。
配合externals
使用
externals
參看下面的解釋
Webpack 是如此的強(qiáng)大,用其打包的腳本可以運(yùn)行在多種環(huán)境下,Web 環(huán)境只是其默認(rèn)的一種,也是最常用的一種。考慮到 Web 上有很多的公用 CDN 服務(wù),那么 怎么將 Webpack 和公用的 CDN 結(jié)合使用呢?方法是使用 externals 聲明一個(gè)外部依賴。
externals: { moment: true}
當(dāng)然了 HTML 代碼里需要加上一行
<script src="//apps.bdimg.com/libs/moment/2.8.3/moment-with-locales.min.js"></script>
執(zhí)行打包后,效果如下:
4.4.2 resolve.extensions
resolve : { extensions: ["", ".webpack.js", ".web.js", ".js", ".less"]}
這項(xiàng)配置的作用是自動(dòng)加上文件的擴(kuò)展名,比如你有如下代碼:
require('style.less');var app = require('./app.js');
那么加上這項(xiàng)配置之后,你可以寫成:
require('style');var app = require('./app');
4.5 externals
當(dāng)我們想在項(xiàng)目中require一些其他的類庫(kù)或者API,而又不想讓這些類庫(kù)的源碼被構(gòu)建到運(yùn)行時(shí)文件中,這在實(shí)際開發(fā)中很有必要。此時(shí)我們就可以通過配置externals參數(shù)來解決這個(gè)問題:
//webpack.config.jsmodule.exports = { externals: { 'react': 'React' }, //...}
externals對(duì)象的key是給require時(shí)用的,比如require('react'),對(duì)象的value表示的是如何在global(即window)中訪問到該對(duì)象,這里是window.React。
同理jquery的話就可以這樣寫:'jquery': 'jQuery',那么require('jquery')即可。
HTML中注意引入順序即可:
<script src="react.min.js" /><script src="bundle.js" />
4.6 devtool
提供了一些方式來使得代碼調(diào)試更加方便,因?yàn)榇虬蟮拇a是合并以后的代碼,不利于排錯(cuò)和定位。其中有如下幾種方式,參見官網(wǎng)devtool
例如,我在basic/app.js
中增加如下配置:
require('./app.css');// 新增hello.js,顯然在文件夾中是不會(huì)存在hello.js文件的,這里會(huì)報(bào)錯(cuò)require('./hello.js');document.getElementById("container").textContent = "APP";
執(zhí)行文件,之后運(yùn)行index.html
,報(bào)錯(cuò)結(jié)果如下:
給出的提示實(shí)在main.bundle.js第48行,點(diǎn)進(jìn)去看其中的報(bào)錯(cuò)如下:
從這里你完全看不出到底你程序的哪個(gè)地方出錯(cuò)了,并且這里的行數(shù)還算少,當(dāng)一個(gè)文件出現(xiàn)了上千行的時(shí)候,你定位bug
的時(shí)間將會(huì)更長(zhǎng)。
增加devtool
文件配置,如下:
module.exports = { devtool: 'eval-source-map', // ....};
執(zhí)行文件,之后運(yùn)行index.html
,報(bào)錯(cuò)結(jié)果如下:
這里發(fā)現(xiàn)直接定位到了app.js
,并且報(bào)出了在第二行出錯(cuò),點(diǎn)擊去看其中的報(bào)錯(cuò)如下:
發(fā)現(xiàn)問題定位一目了然。
5. webpack常用技巧
### 5.1 代碼塊劃分
5.1.1 Commonjs采用require.ensure
來產(chǎn)生chunk
塊
require.ensure(dependencies, callback);//static importsimport _ from 'lodash'// dynamic importsrequire.ensure([], function(require) { let contacts = require('./contacts')})
這一點(diǎn)在output.chunkFileName
中已經(jīng)做過演示,可以去查看
5.1.2 AMD采用require
來產(chǎn)生chunk
塊
require(["module-a", "module-b"], function(a, b) { // ...});
5.1.3 將項(xiàng)目APP代碼與公共庫(kù)文件單獨(dú)打包
我們?cè)?code style="margin: 1px 5px; padding: 0px 5px !important; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: "Courier New", sans-serif !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; border-radius: 3px !important;">basic/app.js中添加如下代碼
var $ = require('juqery'), _ = require('underscore');//.....
然后我們?cè)谂渲梦募刑砑?code style="margin: 1px 5px; padding: 0px 5px !important; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: "Courier New", sans-serif !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; border-radius: 3px !important;">vendor,以及運(yùn)用代碼分離的插件對(duì)生成的vendor
塊重新命名
var webpack = require("webpack");module.exports = { entry: { app: "./app.js", vendor: ["jquery", "underscore", ...], }, output: { filename: "bundle.js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js") ]};
運(yùn)行配置文件,效果如下:
5.1.4 抽取多入口文件的公共部分
我們重新建立一個(gè)文件夾叫做common
,有如下文件:
// common/app1.jsconsole.log("APP1");
// common/app2.jsconsole.log("APP2");
打包之后生成的app1.bundle.js
、app2.bundle.js
中會(huì)存在許多公共代碼,我們可以將它提取出來。
// common/webpack.config.js/** * webpack打包配置文件 * 抽取公共部分js */var webpack = require('webpack');module.exports = { http://www.cnblogs.com/rynxiao/p/7149274.html