webpack 多頁面支持 & 公共組件單獨打包


webpack - 多頁面/入口支持 & 公共組件單獨打包

webpack系列目錄

本系列並非全部原創,如非原創,正文篇首會注明轉載地址

基於webpack搭建純靜態頁面型前端工程解決方案模板, 最終形態源碼見github: https://github.com/ifengkou/webpack-template

正文

本篇主要介紹:如何自動構建入口文件,並生成對應的output;公共js庫如何單獨打包。

多入口文件,自動掃描入口。同時支持SPA和多頁面型的項目
公共js庫如何單獨打包。

上一篇示例,主要介紹如何集成第三方js庫到項目中使用,如jquery。示例的入口只有一個index,而且是將公共js庫連同page.js一起打包到output.js中。那么在開發中會出現,每新增一個頁面模塊,就需要修改webpack.config.js配置文件(增加一個入口),而且如果用到的第三方庫比較多,這樣也容易導致jquery,React等代碼庫重復被合並到打包后的js,導致js體積過大,頁面加載時間過長

基礎結構和准備工作

以下示例基於上一篇進行改進,上一篇項目源碼

目錄結構說明

.
├── package.json              # 項目配置
├── src                       # 源碼目錄
│   ├── pageA.html                # 入口文件a
│   ├── pageB.html                # 入口文件b
│   ├── css/                  # css資源
│   ├── img/                  # 圖片資源
│   ├── js                    # js&jsx資源
│   │   ├── pageA.js              # a頁面入口
│   │   ├── pageB.js              # b頁面入口
│   │   ├── lib/              # 沒有存放在npm的第三方庫或者下載存放到本地的基礎庫,如jQuery、Zepto、avalon
│   ├── pathmap.json          # 手動配置某些模塊的路徑,可以加快webpack的編譯速度
├── webpack.config.js         # webpack配置入口

一:自動構建入口

官方多入口示例

webpack默認支持多入口,官方也有多入口的示例。配件文件webpack.config.js如下

//已簡化
var path = require("path");
module.exports = {
    entry: {
        pageA: "./pageA",
        pageB: "./pageB"
    },
    output: {
        path: path.join(__dirname, "js"),
        filename: "[name].bundle.js",
        chunkFilename: "[id].chunk.js"
    }
}

每新增一個頁面就需要在webpack.config.js的entry 中增加一個 pageC:"./pageC",頁面少還好,頁面一多,就有點麻煩了,而且配置文件,盡可能不改動。那么如何支持不修改配置呢?

自動構建入口函數

entry實際上是一個map對象,結構如下{filename:filepath},那么我們可以根據文件名匹配,很容易構造自動掃描器:
npm 中有一個用於文件名匹配的 glob模塊,通過glob很容易遍歷出src/js目錄下的所有js文件:
安裝glob模塊

$ npm install glob --save-dev

修改webpack.config.js 配置,新增entries函數,修改entry:entries(),修改output的filename為"[name].js"

//引入glob
var glob = require('glob')
//entries函數
var entries= function () {
    var jsDir = path.resolve(srcDir, 'js')
    var entryFiles = glob.sync(jsDir + '/*.{js,jsx}')
    var map = {};

    for (var i = 0; i < entryFiles.length; i++) {
        var filePath = entryFiles[i];
        var filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'));
        map[filename] = filePath;
    }
    return map;
}
//修改入口,已經修改outp的filename
module.exports = {
    //entry: "./src/js/index.js",
    entry: entries(),
    output: {
        path: path.join(__dirname, "dist"),
        filename: "[name].js"
    },
	......
	//以下省略,可以見下文詳細配置

測試

  1. 在src/js目錄中新增pageA.js
//js只有兩行代碼,在body中加一句話
var $ = require("jquery")
$("<div>這是jquery生成的多頁面示例</div>").appendTo("body")
  1. 新增pageA.html,也順便修改原來的index.html 對於js文件名的更改
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<script src="../dist/index.js"></script>
</body>
</html>
  1. 執行webpack,啟動dev-server
$ webpack
$ webpack-dev-server


OK,成功打包生成pageA.js,成功運行

二:公共庫單獨打包

先來分析下,上個步驟打包的日志:

index.js 依賴了avalon 和 jquery,然后打包后的index.js 有480kb
pageA.js 只用了jquery,然后打包后的js 有294kb

那么如果引用的lib庫多一點,又被很多頁面引用,那么lib庫就會被重復打包到page.js中去,模塊越多重復加載的情況越嚴重。

如果把公共代碼提取出來作為單獨的js,那么就到處可以復用,瀏覽器也就可以進行緩存,這時候就需要用到webpack內置插件WebPack.optimize.CommonsChunkPlugin

CommonsChunkPlugin 介紹

使用

new webpack.optimize.CommonsChunkPlugin(options)

Options
翻譯得比較簡單,詳見官方說明:

  • options.name or options.names(string|string[]): 公共模塊的名稱
  • options.filename (string): 公開模塊的文件名(生成的文件名)
  • options.minChunks (number|Infinity|function(module,count) - boolean): 為number表示需要被多少個entries依賴才會被打包到公共代碼庫;為Infinity 僅僅創建公共組件塊,不會把任何modules打包進去。並且提供function,以便於自定義邏輯。
  • options.chunks(string[]):只對該chunks中的代碼進行提取。
  • options.children(boolean):如果為true,那么公共組件的所有子依賴都將被選擇進來
  • options.async(boolean|string):如果為true,將創建一個 option.name的子chunks(options.chunks的同級chunks) 異步common chunk
  • options.minSize(number):所有公共module的size 要大於number,才會創建common chunk

2個常用的例子,更多例子見官方說明:
1.Commons chunk for entries:針對入口文件提取公共代碼

new CommonsChunkPlugin({
  name: "commons",
  // (the commons chunk name)

  filename: "commons.js",
  // (the filename of the commons chunk)

  // minChunks: 3,
  // (Modules must be shared between 3 entries)

  // chunks: ["pageA", "pageB"],
  // (Only use these entries)
})

2.Explicit vendor chunk:直接指定第三方依賴庫,打包成公共組件

entry: {
  vendor: ["jquery", "other-lib"],
  app: "./entry"
}
new CommonsChunkPlugin({
  name: "vendor",

  // filename: "vendor.js"
  // (Give the chunk a different name)

  minChunks: Infinity,
  // (with more entries, this ensures that no other module
  //  goes into the vendor chunk)
})

CommonsChunkPlugin使用

基於上篇的項目,參考上面的第二個例子,我們將jquery 和 avalon 提取出來打包成vendor.js
完整的webpack.config.js 如下:

	var webpack = require("webpack");
	var path = require("path");
	var srcDir = path.resolve(process.cwd(), 'src');
	var nodeModPath = path.resolve(__dirname, './node_modules');
	var pathMap = require('./src/pathmap.json');
	var glob = require('glob')
	var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
	var entries= function () {
	    var jsDir = path.resolve(srcDir, 'js')
	    var entryFiles = glob.sync(jsDir + '/*.{js,jsx}')
	    var map = {};
	
	    for (var i = 0; i < entryFiles.length; i++) {
	        var filePath = entryFiles[i];
	        var filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'));
	        map[filename] = filePath;
	    }
	    return map;
	}
	
	module.exports = {
	    //entry: "./src/js/index.js",
	    //entry: entries(),
	    entry: Object.assign(entries(), {
	        // 用到什么公共lib(例如jquery.js),就把它加進vendor去,目的是將公用庫單獨提取打包
	        'vendor': ['jquery', 'avalon']
	    }),
	    output: {
	        path: path.join(__dirname, "dist"),
	        filename: "[name].js"
	    },
	    module: {
	        loaders: [
	            {test: /\.css$/, loader: 'style-loader!css-loader'}
	        ]
	    },
	    resolve: {
	        extensions: ['.js', "", ".css"],
	        root: [srcDir,nodeModPath],
	        alias: pathMap,
	        publicPath: '/'
	    },
	    plugins: [
	        new CommonsChunkPlugin({
	            name: 'vendor',
	            minChunks: Infinity
	        })
	    ]
	}
	```
### 測試、驗證

1. 修改入口(Object.assign 是html5.js里面的....)
```js
    //entry: entries(),
    entry: Object.assign(entries(), {
        // 用到什么公共lib(例如jquery.js),就把它加進vendor去,目的是將公用庫單獨提取打包
        'vendor': ['jquery', 'avalon']
    }),
  1. 加入插件CommonsChunkPlugin
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
config 中增加 plugins
		plugins: [
            new CommonsChunkPlugin({
                name: 'vendor',
                minChunks: Infinity
            })
        ]
  1. 修改index.html 和 pageA.html,增加對verdor.js的引用
<script src="../dist/vendor.js"></script>
<script src="../dist/index.js"></script>
//<script src="../dist/pageA.js"></script>
  1. 執行webpack
$ webpack

結果分析
可以看到index.js 就只有457 bytes了,pageA.js 227bytes。vendor.js 是集成了jquery+avalon,所以有488kb。
這樣vendor.js 就可以重復利用了,也方便瀏覽器進行緩存。

調試過程中發現

Uncaught ReferenceError: webpackJsonp is not defined

這個是因為當時把vendor.js引入 放到了page.js 后面,導致page.js執行異常,所以,請一定把vendor.js 放在前面。

生成后的index.js就很輕便了,第三方庫都被打包到vendor中了,代碼如下:

webpackJsonp([0],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {

	/**
	 * Created by sloong on 2016/6/1.
	 */
	//avalon 測試
	var avalon = __webpack_require__(1);
	avalon.define({
	    $id: "avalonCtrl",
	    name: "Hello Avalon!"
	});

	/*
	//zepto 測試
	require("zepto")

	$("<div>這是zepto生成的</div>").appendTo("body")*/

	//jquery 測試
	var $ = __webpack_require__(2)
	$("<div>這是jquery生成的</div>").appendTo("body")

/***/ }
]);

頁面測試均正常

OK,本篇結束了。如何讓webpack 自動在html文件中引入所需js的script標簽,如何給js和css文件加了hash值,這樣瀏覽器每次都能檢測到文件變更,而且也不需要手動修改引入的js文件鏈接,這些操作webpack都能輕松搞定


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM