webpack3實戰(5)打包一個多頁、jQuery、圖片轉base64、壓縮混淆、非同步模組載入的專案

qq20004604發表於2017-12-19

前注:

文件全文請檢視 根目錄的文件說明

如果可以,請給本專案加【Star】和【Fork】持續關注。

有疑義請點選這裡,發【Issues】。

實戰專案示例目錄

0、使用說明

安裝:

npm install
複製程式碼

執行(注,這裡不像之前用的 test ,而是改用了 build):

npm run build
複製程式碼

1、需求列表

基本需求:

  1. 引入jQuery(或其他類似庫,之所以用 jQuery 是每個前端開發者都理應會 jQuery);
  2. 使用 less 作為 css 前處理器;
  3. 標準模組化開發;
  4. 有非同步載入的模組;
  5. 使用 es6、es7 語法;
  6. 寫一個登入頁面作為DEMO,再寫一個登入後的示例頁面作為跳轉後頁面;
  7. 可適用於多頁專案;
  8. css 檔案與 圖片 檔案脫離(即更改 css 檔案路徑不影響其對圖片的引用)

打包要求:

  1. 啟用 hash 命名,以應對快取問題;
  2. css 自動新增相容性字首;
  3. 將圖片統一放到同一個資料夾下,方便管理;
  4. 將共同引入的模組單獨打包出來,用於快取,減少每次重複載入的程式碼量;
  5. 程式碼進行醜化壓縮;

2、涉及到的知識

  1. 入口:設定入口檔案;
  2. 出口:設定打包後的資料夾以及檔案命名;
  3. babel-loader:用於將es6、es7等語法,轉換為es5語法;
  4. css-loader:用於處理css檔案(主要是處理圖片的url);
  5. style-loader:將轉換後的css檔案以 style 標籤形式插入 html 中;
  6. postcss-loader:一般用於新增相容性屬性字首;
  7. less-loader:以 less 語法來寫 css ;
  8. url-loader:用於將圖片小於一定大小的檔案,轉為 base64 字串;
  9. file-loaderurl-loader 不能轉換 base64字串 的檔案,被這個處理(主要用於設定打包後圖片路徑,以及CDN等);
  10. html-withimg-loader:用於載入html模板;
  11. html-webpack-plugin :用於將已有 html 檔案作為模板,生成打包後的 html 檔案;
  12. clean-webpack-plugin:用於每次打包前清理dist資料夾
  13. CommonsChunkPlugin:提取 chunks 之間共享的通用模組

3、技術難點

3.1、多頁面

多頁模式是一個難點。

且不考慮共同模組(這裡主要指的是html模板,而不是js的模組),光是單獨每個入口 js 檔案需要搭配一個相對應的 html 檔案,就已經是一件很麻煩的事情了。

對於這個問題,需要藉助使用 html-webpack-plugin 來實現。

由於之前木有 html-webpack-plugin 的相關內容,這裡只講思路和程式碼。

第一:多入口則多個html檔案

也是核心內容,html-webpack-plugin 只負責生成一個 html 檔案。

而多入口顯然需要生成多個 html 檔案,因此 有多少個入口,就需要在 webpack 的 plugins 裡新增多少個 html-webpack-plugin 的例項。

同時,我們還要更改 webpack 的 entry 入口,entry 的值應該是根據入口數量自動生成的物件。

第二:chunks特性實現按需載入

通過配置 html-webpack-pluginoptions.chunks ,可以讓我們實現讓 login.html 只載入 login/index.js,而 userInfo.html 只載入 userInfo/index.js(注:由於以 entry 的 key 作為尋找出口檔案的根據,因此打包後帶 hash 的檔名不影響匹配);

注意,這個實現的機制,是通過 options.chunk 的值,去匹配 webpack.config.jsentry 物件的 key

因為一個入口檔案對應一個出口檔案,所以這裡會去拿入口檔案對應的出口檔案,將其加到 html 檔案裡。

第三:template自定義作為模板的 html 檔案

options.template 可以自定義該例項以哪個 html 檔案作為模板。

第四:filename

options.filename 可以自定義生成的 html 檔案輸出為什麼樣的檔名。

第五:管理多入口

已知:

一個 html-webpack-plugin 例項具有以下功能:

  1. 生成一個 html 檔案(一);
  2. 決定自己引入哪個 js 檔案(二)(記得,webpack只負責打包js檔案,不負責生成 html 檔案。生成例項是依靠這個 plugins);
  3. 決定自己以哪個 html 檔案作為模板(三);
  4. 決定自己打包後的目錄和檔名(四);

我們通過webpack打包後,一個入口 js 檔案會對應一個出口 js 檔案;

而每個入口 js 檔案,都對應一個 html 模板檔案;

因此每個 html 模板檔案,都知道自己對應哪個出口 js 檔案;

所以以上是實現多入口的原理。

程式碼:

多入口管理檔案:

config/entry.json

[
    {
        "url": "login",
        "title": "登入"
    },
    {
        "url": "userInfo",
        "title": "使用者詳細資訊"
    }
]
複製程式碼

webpack配置檔案:

webpack.config.js:

首先,配置 entry

const entryJSON = require('../config/entry.json');

// 入口管理
let entry = {}
entryJSON.map(page => {
    entry[page.url] = path.resolve(__dirname, `../src/page/${page.url}/index.js`)
})
複製程式碼

其次,配置 plugins

// 在上面已經引用了 entryJSON
const path = require('path')

// 因為多入口,所以要多個HtmlWebpackPlugin,每個只能管一個入口
let plugins = entryJSON.map(page => {
    return new HtmlWebpackPlugin({
        filename: path.resolve(__dirname, `../dist/${page.url}.html`),
        template: path.resolve(__dirname, `../src/page/${page.url}/index.html`),
        chunks: [page.url], // 實現多入口的核心,決定自己載入哪個js檔案,這裡的 page.url 指的是 entry 物件的 key 所對應的入口打包出來的js檔案
        hash: true, // 為靜態資源生成hash值
        minify: false,   // 壓縮,如果啟用這個的話,需要使用html-minifier,不然會直接報錯
        xhtml: true,    // 自閉標籤
    })
})
複製程式碼

最後,webpack 本身的配置:

module.exports = {
    // 入口檔案
    entry: entry,
        // 出口檔案
    output: {
        path: __dirname + '/../dist',
        // 檔名,將打包好的匯出為bundle.js
        filename: '[name].[hash:8].js'
    },
    // 省略中間的配置
    // 將外掛新增到webpack中
    plugins: plugins
}
複製程式碼

檔案目錄(已省略無關檔案):

├─build
│  └─webpack.config.js
├─dist
└─src
    └─page
       ├─login
       │  ├─index.js
       │  ├─index.html
       │  └─login.less
       └─userInfo
          ├─index.js
          └─index.html
複製程式碼

3.2、檔案分類管理

如何將頁面整齊的分類,也是很重要的。不合理的規劃,會增加專案的維護難度。

專案目錄如下分類:

├─build     webpack 的配置檔案,例如 webpack.config.js
├─config    跟 webpack 有關的配置檔案,例如 postcss-loader 的配置檔案,以及多入口管理檔案
├─dist      打包的目標資料夾,存放 html 檔案
│  └─img    打包後的圖片資料夾
└─src       資原始檔夾
    ├─common    全域性配置,或者公共方法,放在此資料夾,例如 less-loader 的全域性變數
    ├─img       圖片資原始檔夾,這些是共用的圖片
    ├─less      less 資料夾,共用的less檔案
    ├─page      每個頁面,在page裡會有一個資料夾,裡面放置入口 js 檔案,源 html 檔案,以及不會被複用的 html template檔案。
    ├─template  html 模板資料夾(通過js引入模板,這裡的可能被複用)
    └─static    靜態資原始檔夾,這裡放使用靜態路徑的資源
複製程式碼

雖然還不夠精細,但應對小型專案是足夠了的。

3.3、別名

別名的優勢很多,比如:

1、css/less 程式碼,可以和圖片分離:

只要 webpack 配置和圖片的位置不變。

那麼使用別名,就可以隨意移動 less 檔案。

不必擔心因為移動 less 檔案,而造成的 less 檔案與 圖片 檔案的相對路徑改變,導致找不到圖片而出錯。

2、方便整體移動圖片

假如原本圖片放在src/img資料夾下,現在你突然想把圖片放在src/image資料夾下。

如果不使用別名,你需要一個一個去修改圖片的路徑;

而使用別名,只需要改一下別名的路徑就行了。

css-loader 支援獨立於 webpack 的別名的設定,教程參照:css-loader

這裡基於【3.2】的檔案分類管理,附上關於別名的控制程式碼:

{
    loader: 'css-loader',
    options: {
        root: path.resolve(__dirname, '../src/static'),   // url裡,以 / 開頭的路徑,去找src/static資料夾
        minimize: true, // 壓縮css程式碼
        // sourceMap: true,    // sourceMap,預設關閉
        alias: {
            '@': path.resolve(__dirname, '../src/img') // '~@/logo.png' 這種寫法,會去找src/img/logo.png這個檔案
        }
    }
},
複製程式碼

其餘程式碼已省略,如果有需要,請檢視 DEMO 中的 build/webpack.config.js 檔案。

3.4、安裝jQuery

方案:

由於npm上並沒有最新的 jQuery,目前來說, 1.7.4 是最新的版本。

所以可以從下面這個CDN直接下載 jQuery 來使用,版本是 1.12.4

https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js

然後在js檔案的開始位置,通過require引入(注意,不能通過 import 引入)

const $ = require('../../common/jquery.min')
複製程式碼

webpack會幫你做剩下的事情,你只需要愉快的使用 jQuery 就好了。

3.5、提取 chunks 之間共享的通用模組

在 3.4 中,我們引入了 jQeury,方法簡單易行,但又一個缺點,那就是會導致程式碼重複打包的問題。

即 jQuery 會被打包進每一個引入他的入口 js 檔案中,每個頁面都需要重複下載一份將jQuery程式碼打包到其中的 js 檔案(很可能兩個 js 檔案只有 20kb 是自己的程式碼,卻有 90kb 是 jQuery 程式碼)。

我們期望:

  1. 訪問第一個頁面時,預期載入 foo.js 和 jQuery.js;
  2. 訪問第二個頁面時,預期載入 bar.js 和 jQuery.js;
  3. 當訪問第二個頁面時,發現已經在第一個頁面下載過 jQuery.js 了,因此將不需要再下載 jQuery 程式碼,只需要下載 bar.js 就可以了;

方案改進:

為了實現這個目標,毫無疑問,我們需要將 jQuery.js 檔案單獨打包,或者說,每一個在多個模組中共享的模組,都會被單獨打包。

有幾種做法,但實測後都不好用,鑑於 jQuery 會在每個頁面都適用,因此綜合考慮後,我採用以下方案來初步實現我的目標。

最後我採用了 webpack 自帶的外掛:webpack.optimize.CommonsChunkPlugin來實現,他可以將在多個檔案中引入的 模組,單獨打包。

關於這個外掛可以先參考官方文件:CommonsChunkPlugin: 提取 chunks 之間共享的通用模組

為了實現我們的目的,我們需要做兩件事:

1、使用這個外掛,如下配置:

const webpack = require('webpack')

new webpack.optimize.CommonsChunkPlugin({
    name: "foo", // 這個對應的是 entry 的 key
    minChunks: 2
})
複製程式碼

這個的效果是將至少有 2 個 chunk 引入的公共程式碼,打包到 foo 這個 chunk 中。

2、我們需要引入這個打包後的 chunk ,方法是通過 html-webpack-plugin 這個外掛引入。

// 無關配置已經省略
new HtmlWebpackPlugin({
    chunks: [page.url, 'foo'], // 這裡的foo,就是通過CommonsChunkPlugin生成的chunk
})
複製程式碼

無需修改原始碼,此時我們可以執行npm run build檢視打包後的效果:

foo.d78e8f4193f50cc42a49.js    // 199 KB(這裡包含jQuery以及公共程式碼)
login.d2819f642c5927565e7b.js  // 15 KB
userInfo.1610748fb3346bcd0c47.js // 4 KB
0.fe5c2c427675e10b0d3a.js      // 2 KB
複製程式碼

注:

如果頁面很多的話,那麼很可能某些公共組建被大量chunk所共享,而某些chunk又被少量chunk所共享。

因此可能需要特殊配置 minChunks 這個屬性,具體請檢視官方文件。

3.6、每次打包前,清理dist資料夾

需要藉助 clean-webpack-plugin 這個外掛。

使用這個外掛後,可以在每次打包前清理掉整個資料夾。

基於本專案來說,清除的時候配置的時候需要這樣配置:

new CleanWebpackPlugin(path.resolve(__dirname, '../dist'), {
    root: path.resolve(__dirname, '../'),    // 設定root
    verbose: true
})
複製程式碼

原因在於,這個外掛會認為webpack.config.js所在的目錄為專案的根目錄。

只使用第一個引數的話,會報錯移除目標的目錄位置不對:

clean-webpack-plugin: (略)【實戰5】打包一個具有常見功能的多頁專案\dist is outside of the project root. Skipping...
複製程式碼

而新增了第二個引數的設定後,就可以正常使用了。

注:

他的效果是直接刪除資料夾,因此千萬別寫錯目錄了,如果刪除了你正常的資料夾,那麼……就只能哭啦。

3.7、使用 html 模板

由於我們很可能在 html 中使用 <img> 標籤,

html-webpack-plugin 這個外掛,只能用於將某個 html 檔案作為打包後的源 html 檔案,

不會將其 <img> 標籤中的 src屬性轉為打包後的圖片路徑,同時也不會將引入的圖片進行打包。

因此我們需要將 html 內容單獨拆出來,page 資料夾裡的原始檔只負責作為 html 模板而已。

為了使用 html 模板,我們需要專門引入一個外掛:

html-withimg-loader:用於解析 html 檔案。

使用方法很簡單:

  1. 配置loader(參照 webpack.config.js);
  2. import 匯入 html 模板檔案(例如 login.html);

匯入的時候,是一個字串,並且圖片的 url 已經被解析了。然後我們將其引入源 html 檔案中(比如page/login.html),再寫各種邏輯就行了。

注:

務必記得先把 html 模板插入頁面中,再寫他的相關邏輯。

3.8、程式碼的醜化壓縮

使用外掛 UglifyjsWebpackPlugin ,文件參照 (UglifyjsWebpackPlugin)[https://doc.webpack-china.org/plugins/uglifyjs-webpack-plugin]

壓縮前:

0.fe5c2c427675e10b0d3a.js    // 2 KB
foo.a5e497953a435f418876.js    // 199 KB
login.9698d39e5b8f6c381649.js    // 15 KB
userInfo.f5a705ffcb43780bb3d6.js    // 4 KB
複製程式碼

醜化壓縮後:


0.fe5c2c427675e10b0d3a.js    // 1 KB
foo.a5e497953a435f418876.js    // 120 KB
login.9698d39e5b8f6c381649.js    // 10 KB
userInfo.f5a705ffcb43780bb3d6.js    // 2 KB
複製程式碼

4、分析

重新列出所有需求:

基本需求:

  1. 引入jQuery(或其他類似庫,之所以用 jQuery 是每個前端開發者都理應會 jQuery);
  2. 使用 less 作為 css 前處理器;
  3. 標準模組化開發;
  4. 有非同步載入的模組;
  5. 使用 es6、es7 語法;
  6. 寫一個登入頁面作為DEMO,再寫一個登入後的示例頁面作為跳轉後頁面;
  7. 可適用於多頁專案;
  8. css 檔案與 圖片 檔案脫離(即更改 css 檔案路徑不影響其對圖片的引用)

打包要求:

  1. 啟用 hash 命名,以應對快取問題;
  2. css 自動新增相容性字首;
  3. 將圖片統一放到同一個資料夾下,方便管理;
  4. 將共同引入的模組單獨打包出來,用於快取,減少每次重複載入的程式碼量;
  5. 程式碼進行醜化壓縮;

需求的實現:

基本需求:

需求的實現過程
需求 實現方法
引入jQuery 1. 通過 require() 引入,並通過 CommonsChunkPlugin 實現單獨打包;
使用 less 作為 css 前處理器 1. 使用 less-loader 來處理 .less 檔案;
標準模組化開發 1. 使用 import 和 require 語法來進行模組化開發;
有非同步載入的模組 1. 通過 require([], callback) 來實現模組的非同步載入
使用 es6、es7 語法 1. 使用 babel 來轉義
寫一個登入頁面作為DEMO,再寫一個登入後的示例頁面作為跳轉後頁面 1. 登入頁:page/login
2. 跳轉後頁面:page/userInfo
可適用於多頁專案 1. config/entry.json 用於配置多頁入口;
2. html-withimg-loader 來生成多頁模板;
3. 最後在webpack.config.js裡配置 entry 和 plugins
css 檔案與 圖片 檔案脫離(即更改 css 檔案路徑不影響其對圖片的引用) 通過 css-loader 的別名實現

打包需求:

需求的實現過程
需求 實現方法
啟用 hash 命名,以應對快取問題 配置 output 的 filename 屬性,加 [chunkhash] 即可
css 自動新增相容性字首 使用 post-loader 的 autoprefixer
將圖片統一放到同一個資料夾下,方便管理 配置 url-loader (實質是 file-loader )的 outputPath
將共同引入的模組單獨打包出來,用於快取,減少每次重複載入的程式碼量 使用外掛 CommonsChunkPlugin 來實現
程式碼進行醜化壓縮 使用外掛 UglifyjsWebpackPlugin 來實現

相關文章