本文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可以做一些什么事情

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

圖中已經(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,打包信息如下

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

生成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,打包信息如下

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

可見生成了兩個(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ì)被替換。如下圖:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)


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ì)生成帶有chunkhashchunk文件,如下圖:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

這在做給文件打版本號(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è)變量

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)


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方式輸出,如圖:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)


### 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中的includeexclude

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

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

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é)果:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

其中會(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'
    }}

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

有沒有發(fā)現(xiàn)打包的時(shí)間已經(jīng)被大大縮短,并且也只產(chǎn)生了兩個(gè)隱藏文件。

配合module.noParse使用

module.noParse參看上面的解釋

noParse: [/moment-with-locales/]

執(zhí)行打包后,效果如下:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

是不是發(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í)行打包后,效果如下:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)


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é)果如下:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

給出的提示實(shí)在main.bundle.js第48行,點(diǎn)進(jìn)去看其中的報(bào)錯(cuò)如下:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

從這里你完全看不出到底你程序的哪個(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é)果如下:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

這里發(fā)現(xiàn)直接定位到了app.js,并且報(bào)出了在第二行出錯(cuò),點(diǎn)擊去看其中的報(bào)錯(cuò)如下:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

發(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)行配置文件,效果如下:

Android培訓(xùn),安卓培訓(xùn),手機(jī)開發(fā)培訓(xùn),移動(dòng)開發(fā)培訓(xùn),云培訓(xùn)培訓(xù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