前注:
文檔全文請查看 根目錄的文檔說明。
如果可以,請給本項目加【Star】和【Fork】持續關注。
有疑義請點擊這里,發【Issues】。
0、使用說明
安裝:
npm install
運行(注,這里不像之前用的 test ,而是改用了 build):
npm run build
1、需求列表
基本需求:
- 引入jQuery(或其他類似庫,之所以用
jQuery
是每個前端開發者都理應會 jQuery); - 使用
less
作為css
預處理器; - 標准模塊化開發;
- 有異步加載的模塊;
- 使用 es6、es7 語法;
- 寫一個登錄頁面作為DEMO,再寫一個登錄后的示例頁面作為跳轉后頁面;
- 可適用於多頁項目;
- css 文件與 圖片 文件脫離(即更改 css 文件路徑不影響其對圖片的引用)
打包要求:
- 啟用 hash 命名,以應對緩存問題;
- css 自動添加兼容性前綴;
- 將圖片統一放到同一個文件夾下,方便管理;
- 將共同引入的模塊單獨打包出來,用於緩存,減少每次重復加載的代碼量;
- 代碼進行丑化壓縮;
2、涉及到的知識
- 入口:設置入口文件;
- 出口:設置打包后的文件夾以及文件命名;
- babel-loader:用於將es6、es7等語法,轉換為es5語法;
- css-loader:用於處理css文件(主要是處理圖片的url);
- style-loader:將轉換后的css文件以 style 標簽形式插入 html 中;
- postcss-loader:一般用於添加兼容性屬性前綴;
- less-loader:以 less 語法來寫 css ;
- url-loader:用於將圖片小於一定大小的文件,轉為 base64 字符串;
- file-loader:
url-loader
不能轉換 base64字符串 的文件,被這個處理(主要用於設置打包后圖片路徑,以及CDN等); - html-withimg-loader:用於加載html模板;
html-webpack-plugin
:用於將已有 html 文件作為模板,生成打包后的 html 文件;clean-webpack-plugin
:用於每次打包前清理dist文件夾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-plugin
的 options.chunks
,可以讓我們實現讓 login.html
只加載 login/index.js
,而 userInfo.html
只加載 userInfo/index.js
(注:由於以 entry 的 key 作為尋找出口文件的根據,因此打包后帶 hash 的文件名不影響匹配);
注意,這個實現的機制,是通過 options.chunk
的值,去匹配 webpack.config.js
的 entry
對象的 key
。
因為一個入口文件對應一個出口文件,所以這里會去拿入口文件對應的出口文件,將其加到 html 文件里。
第三:template自定義作為模板的 html 文件
options.template
可以自定義該實例以哪個 html 文件作為模板。
第四:filename
options.filename
可以自定義生成的 html 文件輸出為什么樣的文件名。
第五:管理多入口
已知:
一個 html-webpack-plugin
實例具有以下功能:
- 生成一個 html 文件(一);
- 決定自己引入哪個 js 文件(二)(記得,webpack只負責打包js文件,不負責生成 html 文件。生成實例是依靠這個 plugins);
- 決定自己以哪個 html 文件作為模板(三);
- 決定自己打包后的目錄和文件名(四);
我們通過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 代碼)。
我們期望:
- 訪問第一個頁面時,預期加載 foo.js 和 jQuery.js;
- 訪問第二個頁面時,預期加載 bar.js 和 jQuery.js;
- 當訪問第二個頁面時,發現已經在第一個頁面下載過 jQuery.js 了,因此將不需要再下載 jQuery 代碼,只需要下載 bar.js 就可以了;
方案改進:
為了實現這個目標,毫無疑問,我們需要將 jQuery.js 文件單獨打包,或者說,每一個在多個模塊中共享的模塊,都會被單獨打包。
有幾種做法,但實測后都不好用,鑒於 jQuery 會在每個頁面都適用,因此綜合考慮后,我采用以下方案來初步實現我的目標。
最后我采用了 webpack 自帶的插件:webpack.optimize.CommonsChunkPlugin
來實現,他可以將在多個文件中引入的 模塊,單獨打包。
關於這個插件可以先參考官方文檔:[CommonsChunkPlugin:
提取 chunks 之間共享的通用模塊](https://doc.webpack-china.org/plugins/commons-chunk-plugin/)。
為了實現我們的目的,我們需要做兩件事:
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 文件。
使用方法很簡單:
- 配置
loader
(參照webpack.config.js
); 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、分析
重新列出所有需求:
基本需求:
- 引入jQuery(或其他類似庫,之所以用
jQuery
是每個前端開發者都理應會 jQuery); - 使用
less
作為css
預處理器; - 標准模塊化開發;
- 有異步加載的模塊;
- 使用 es6、es7 語法;
- 寫一個登錄頁面作為DEMO,再寫一個登錄后的示例頁面作為跳轉后頁面;
- 可適用於多頁項目;
- css 文件與 圖片 文件脫離(即更改 css 文件路徑不影響其對圖片的引用)
打包要求:
- 啟用 hash 命名,以應對緩存問題;
- css 自動添加兼容性前綴;
- 將圖片統一放到同一個文件夾下,方便管理;
- 將共同引入的模塊單獨打包出來,用於緩存,減少每次重復加載的代碼量;
- 代碼進行丑化壓縮;
需求的實現:
基本需求:
需求的實現過程 | |
需求 | 實現方法 |
引入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 來實現 |