這一次,我總結和分享一項大型網站優化技術,那就是在項目中自動壓縮靜態資源文件(css、js),並讓網站自動加載壓縮後的資源文件。固然,這項技術在雅虎35條前端優化建議裏也有記載,但它那只是給出一個理論的方案而已,而且採用的是外部壓縮工具去壓縮,而在個人項目中,是直接經過本身的程序自動化去壓縮全部css、js文件,而後讓頁面直接加載所壓縮後的資源,接下來直接進入主題。javascript
本次實驗使用的是PHP腳本語言,版本是PHP5.6,是在LINUX下搭建的環境(網上搭建不管是搭建LAMP仍是LNMP的教程都五花八門亂七八糟,下次我會總結和分享如何在LINUX下搭建服務器環境的博文,並且搭建的環境必須一次性搭建成功的)。所選用的框架是CI框架,所使用的模板是Smarty模板引擎。固然了,這些只是我所使用的環境而已,若是你是PHP開發者,假如你要測試下此次實驗,那麼,我建議你的PHP版本選用5.4以上,至於框架用什麼都是能夠的。而若是你不是PHP開發者(你是JSP或者是ASP開發者或者是其餘開發者),那麼你理解好這一思路後,徹底能夠在本身熟悉的語言裏進行實驗測試。php
1、原理圖css
首先我畫一張思路圖,便於你們先理解。html
首先是資源壓縮原理圖:前端
接着是資源文件替換的原理圖:java
假如你們認真理解而且看懂這兩張原理圖的話,基本上也就掌握了我所分享的思路。假如仍是不能理解的話,接下來我會結合代碼,對以上原理圖的每一步進行詳細講解。jquery
2、思路詳細分析redis
1.首先是調用該壓縮的方法,你能夠把該方法放在網站所要加載的公共類的地方,例如每次訪問網站都會調用該壓縮方法進行壓縮。固然,這個只是在開發環境纔會每次都調用,若是是線上的環境,在你的網站發一次新版本的時候,調用一次用來生成壓縮版的靜態資源就能夠了。apache
1 class MY_Controller extends CI_Controller { 2 public function __construct() { 3 parent::__construct(); 4 5 //壓縮jscss資源文件 6 $this->compressResHandle(); 7 } 8 /** 9 * 壓縮js、css資源文件(優化) 10 * @return [type] [description] 11 */ 12 private function compressResHandle() { 13 $this->load->library('ResMinifier'); 14 //壓縮指定文件夾下的資源文件 15 $this->resminifier->compressRes(); 16 } 17 }
2.接着就調用了 ResMinifier類裏的 compressRes方法。在這裏我先附上 ResMinifier這個類的代碼,而後方便一步步進行分析講解數組
1 <?php 2 defined('BASEPATH') OR exit('No direct script access allowed'); 3 /** 4 * 資源壓縮類 5 */ 6 class ResMinifier { 7 /** 須要壓縮的資源目錄*/ 8 public $compressResDir = ['css', 'js']; 9 /** 忽略壓縮的路徑,例如此處是js/icon開頭的路徑忽略壓縮*/ 10 public $compressResIngorePrefix = ['js/icon']; 11 /** 資源根目錄*/ 12 public $resRootDir; 13 /** 資源版本文件路徑*/ 14 private $resStatePath; 15 16 public function __construct() { 17 $this->resRootDir = WEBROOT . 'www/'; 18 $this->resStatePath = WEBROOT . 'www/resState.php'; 19 } 20 21 public function compressRes() { 22 //獲取存放版本的資源文件 23 $resState = $this->getResState(); 24 $count = 0; 25 26 //開始遍歷須要壓縮的資源目錄 27 foreach ($this->compressResDir as $resDir) { 28 foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->resRootDir . $resDir , FilesystemIterator::SKIP_DOTS)) as $file) { 29 //獲取該資源文件的絕對路徑 30 $filePath = str_replace('\\', '/', $file->getRealPath()); 31 //獲取文件相對路徑 32 $object = substr($filePath, strlen($this->resRootDir)); 33 //計算文件的版本號 34 $state = $this->_getResStateVersion($filePath); 35 36 //獲取文件的幾個參數值 37 if (true !== $this->getObjectInfo($object, $minObject, $needCompress, $state, $extension)) { 38 continue; 39 } 40 41 //壓縮文件的絕對路徑 42 $minFilePath = str_replace('\\', '/', $this->resRootDir. $minObject); 43 44 //************此處p判斷是最重要部分之一*****************// 45 //判斷文件是否存在且已經改動過 46 if (isset($resState[$object]) && $resState[$object] == $state && isset($resState[$minObject]) && file_exists($minFilePath)) { 47 continue; 48 } 49 50 //確保/www/min/目錄可寫 51 $this->_ensureWritableDir(dirname($minFilePath)); 52 53 if ($needCompress) { 54 $this->compressResFileAndSave($filePath, $minFilePath); 55 } else { 56 copy($filePath, $minFilePath); 57 } 58 59 60 $resState[$object] = $state; 61 $resState[$minObject] = ''; 62 $count++; 63 64 if ($count == 50) { 65 $this->_saveResState($resState); 66 $count = 0; 67 } 68 69 } 70 } 71 if($count) $this->_saveResState($resState); 72 } 73 74 public function getObjectInfo($object, &$minObject, &$needCompress, &$state, &$extension) { 75 //獲取資源絕對路徑 76 $filePath = $this->resRootDir . $object; 77 //判斷資源是否存在 78 if (!file_exists($filePath)) return "資源文件不存在{$filePath}"; 79 //版本號 80 $state = $this-> _getResStateVersion($filePath); 81 //文件名後綴 82 $extension = pathinfo($filePath, PATHINFO_EXTENSION); 83 //是否要壓縮 84 $needCompress = true; 85 86 //判斷資源文件是不是以 .min.css或者.min.js結尾的 87 //此類結尾通常都是已壓縮過,例如jquery.min.js,就沒必要再壓縮了 88 if (str_end_with($object, '.min.'.$extension, true)) { 89 //壓縮後的資源存放路徑,放在 /www/min/ 目錄下 90 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state .'.'. $extension; 91 $needCompress = false; 92 } else if (in_array($extension, $this->compressResDir)) { 93 //此處是須要壓縮的文件目錄 94 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension; 95 //看看是不是忽略的路徑前綴 96 foreach ($this->compressResIngorePrefix as $v) { 97 if (str_start_with($object, $v, true)) { 98 $needCompress = false; 99 } 100 } 101 } else { 102 $minObject = 'min/'.$object; 103 $needCompress = false; 104 } 105 return true; 106 } 107 108 109 /** 110 * 獲取存放資源版本的文件 111 * 它是放在一個數組裏 112 * $resState = array( 113 * '文件路徑' => '對應的版本號', 114 * '文件路徑' => '對應的版本號', 115 * '文件路徑' => '對應的版本號', 116 * ); 117 * @return [type] [description] 118 */ 119 public function getResState() { 120 if (file_exists($this->resStatePath)) { 121 require $this->resStatePath; 122 return $resState; 123 } 124 return []; 125 } 126 127 /** 128 * 計算文件的版本號,這個是根據計算文件MD5散列值獲得版本號 129 * 只要文件內容改變了,所計算獲得的散列值就會不同 130 * 用於判斷資源文件是否有改動過 131 * @param [type] $filePath [description] 132 * @return [type] [description] 133 */ 134 public function _getResStateVersion($filePath) { 135 return base_convert(crc32(md5_file($filePath)), 10, 36); 136 } 137 138 /** 139 * 確保目錄可寫 140 * @param [type] $dir [description] 141 * @return [type] [description] 142 */ 143 private function _ensureWritableDir($dir) { 144 if (!file_exists($dir)) { 145 @mkdir($dir, 0777, true); 146 @chmod($dir, 0777); 147 } else if (!is_writable($dir)) { 148 @chmod($dir, 0777); 149 if (!is_writable($dir)) { 150 show_error('目錄'.$dir.'不可寫'); 151 } 152 } 153 } 154 155 /** 156 * 將壓縮後的資源文件寫入到/www/min/下去 157 * @param [type] $filePath [description] 158 * @param [type] $minFilePath [description] 159 * @return [type] [description] 160 */ 161 private function compressResFileAndSave($filePath, $minFilePath) { 162 if (!file_put_contents($minFilePath, $this->compressResFile($filePath))) { 163 164 //$CI->exceptions->show_exception("寫入文件{$minFilePath}失敗"); 165 show_error("寫入文件{$minFilePath}失敗", -1); 166 } 167 } 168 169 /** 170 * 壓縮資源文件 171 * @param [type] $filePath [description] 172 * @return [type] [description] 173 */ 174 private function compressResFile($filePath) { 175 $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); 176 if ($extension === 'js') { 177 require_once 'JShrink/Minifier.php'; 178 return \JShrink\Minifier::minify(file_get_contents($filePath)); 179 } else if ($extension ==='css') { 180 $content = file_get_contents($filePath); 181 $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content); 182 $content = str_replace(["\r\n", "\r", "\n"], '', $content); 183 $content = preg_replace('/([{}),;:>])\s+/', '$1', $content); 184 $content = preg_replace('/\s+([{}),;:>])/', '$1', $content); 185 $content = str_replace(';}', '}', $content); 186 return $content; 187 } else { 188 //$CI->exceptions->show_exception("不支持壓縮{extension}文件[$filePath]"); 189 show_error("不支持壓縮{extension}文件[$filePath]", -1); 190 191 } 192 } 193 194 private function _saveResState($resState) { 195 ksort($resState); 196 $content = "<?php\n\n\$resState = array(\n"; 197 foreach ($resState as $k => $v) { 198 $content .= "\t '$k' => '$v',\n"; 199 } 200 $content .= ");\n\n"; 201 file_put_contents($this->resStatePath, $content); 202 } 203 204 }
整個類大部分代碼我都加了註釋,方便你們快速理解。這裏我也會對每一行代碼進行解說。
(1)
/** 須要壓縮的資源目錄*/ public $compressResDir = ['css', 'js']; /** 忽略壓縮的路徑,例如此處是js/icon開頭的路徑忽略壓縮*/ public $compressResIngorePrefix = ['js/icon']; /** 資源根目錄*/ public $resRootDir; /** 資源版本文件路徑*/ private $resStatePath; public function __construct() { $this->resRootDir = WEBROOT . 'www/'; $this->resStatePath = WEBROOT . 'www/resState.php'; }
$compressResDir變量是須要壓縮的資源目錄,假如你有新的處理目錄,能夠在此變量裏假如新的目錄名便可處理。附上我測試項目的目錄圖
$compressResIngorePrefix 忽略被壓縮的路徑的路徑前部分是該數組變量的字符串,例如 有一個資源路徑爲 js/icon/bg.js或者是js/icon_index.js或者是js/icon.header.js,假如在該數組中加入了 js/icon這個字符串,那麼資源路徑爲js/icon開頭的都會被忽略掉,也就是直接跳過,不用壓縮。(由於資源文件裏總有一些是不須要壓縮的嘛)
$resRootDir存放資源根目錄的
$resStatePath 這個是資源版本文件路徑
(2)進入compressRes() 方法,咱們先分析前面這一段代碼
public function compressRes() { //獲取存放版本的資源文件 $resState = $this->getResState(); $count = 0;
-------------------------------調用getResState() 講解start-------------------------------------------------------------
這裏首先是調用 $this->getResState() 方法來獲取存放版本的資源文件,此處先跳到該方法看看是如何寫的,其實就是包含該文件,而後返回裏面存放版本號的數組,咱們看註釋能夠知道該文件裏存放版本號的格式(順便附上圖讓你們看看)
/** * 獲取存放資源版本的文件 * 它是放在一個數組裏 * $resState = array( * '文件路徑' => '對應的版本號', * '文件路徑' => '對應的版本號', * '文件路徑' => '對應的版本號', * ); * @return [type] [description] */ public function getResState() { if (file_exists($this->resStatePath)) { require $this->resStatePath; return $resState; } return []; }
(資源版本文件截圖:)
-------------------------------調用getResState() 講解end-------------------------------------------------------------
接着看compressRes()裏的這一段代碼
//開始遍歷須要壓縮的資源目錄 foreach ($this->compressResDir as $resDir) { foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->resRootDir . $resDir , FilesystemIterator::SKIP_DOTS)) as $file) { //獲取該資源文件的絕對路徑 $filePath = str_replace('\\', '/', $file->getRealPath()); //獲取文件相對路徑 $object = substr($filePath, strlen($this->resRootDir)); //計算文件的版本號 $state = $this->_getResStateVersion($filePath);
第一個遍歷的是js和css目錄 第二個遍歷是將js目錄或者css目錄裏的文件都變成路徑形式,
例如獲取文件的絕對路徑 $filePath 的值是這樣子的:
/usr/local/apache2/htdocs/project/www/css/home/index.css
而文件的相對路徑$object是這樣子的 :
css/home/index.css
這裏就開始調用$this->_getResStateVersion($filePath)來計算文件的版本號
-------------------------------調用_getResStateVersion($filePath) 講解start-------------------------------------------------------------
/** * 計算文件的版本號,這個是根據計算文件MD5散列值獲得版本號 * 只要文件內容改變了,所計算獲得的散列值就會不同 * 用於判斷資源文件是否有改動過 * @param [type] $filePath [description] * @return [type] [description] */ public function _getResStateVersion($filePath) { return base_convert(crc32(md5_file($filePath)), 10, 36); }
-------------------------------調用_getResStateVersion($filePath) 講解end-------------------------------------------------------------
或者到版本號後,再看下一段代碼,這裏開始調用$this->getObjectInfo()方法,這裏獲取到壓縮文件的相對路徑$minObject,是否須要壓縮$needCompress,版本號$state,文件後綴$extension。
//獲取文件的幾個參數值 if (true !== $this->getObjectInfo($object, $minObject, $needCompress, $state, $extension)) { continue; }
-------------------------------調用$this->getObjectInfo() 講解start-------------------------------------------------------------
/** * 獲取資源文件相關信息 * @param [type] $object 資源文件路徑 (www/css/home/index.css) * @param [type] $minObject 壓縮資源文件路徑 (www/min/css/home/index.ae123a.css) * @param [type] $needCompress 是否須要壓縮 * @param [type] $state 文件版本號 * @param [type] $extension 文件名後綴 * @return [type] [description] */ public function getObjectInfo($object, &$minObject, &$needCompress, &$state, &$extension) { //獲取資源絕對路徑 $filePath = $this->resRootDir . $object; //判斷資源是否存在 if (!file_exists($filePath)) return "資源文件不存在{$filePath}"; //版本號 $state = $this-> _getResStateVersion($filePath); //文件名後綴 $extension = pathinfo($filePath, PATHINFO_EXTENSION); //是否要壓縮 $needCompress = true; //判斷資源文件是不是以 .min.css或者.min.js結尾的 //此類結尾通常都是已壓縮過,例如jquery.min.js,就沒必要再壓縮了 if (str_end_with($object, '.min.'.$extension, true)) { //壓縮後的資源存放路徑,放在 /www/min/ 目錄下 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state .'.'. $extension; $needCompress = false; } else if (in_array($extension, $this->compressResDir)) { //此處是須要壓縮的文件目錄 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension; //看看是不是忽略的路徑前綴 foreach ($this->compressResIngorePrefix as $v) { if (str_start_with($object, $v, true)) { $needCompress = false; } } } else { $minObject = 'min/'.$object; $needCompress = false; } return true; }
這個方法裏的每一行代碼基本上都有註釋了,因此就不一句句進行講解了,這裏主要看下面的判斷部分:
if (str_end_with($object, '.min.'.$extension, true)) 這個判斷是比較資源文件路徑字串後面部分是否以 .min.$extension 結尾,例如是 jquery.min.js,這種文件原本就是
壓縮過的文件,因此就不用再進行壓縮處理了, $minObject 這個變量存放的是壓縮後的資源文件路徑。
此處附上str_end_with()函數的代碼:
/** * 判斷 subject 是否以 search結尾, 參數指定是否忽略大小寫 * @param [type] $subject [description] * @param [type] $search [description] * @param boolean $ignore_case [description] * @return [type] [description] */ function str_end_with($subject, $search, $ignore_case = false) { $len2 = strlen($search); if (0 === $len2) return true; $len1 = strlen($subject); if ($len2 > $len1) return false; if ($ignore_case) { return 0 === strcmp(substr($subject, $len1 - $len2), $search); } else { return 0 === strcasecmp(substr($subject, $len1 - $len2), $search); } }
if (in_array($extension, $this->compressResDir),這個判斷就是是不是須要處理的兩個目錄裏的。
而後裏面的foreach ($this->compressResIngorePrefix as $v) { if (str_start_with($object, $v, true)) { $needCompress = false; } }
這個是判斷是不是以$this->compressResIngorePrefix屬性定義的前面部分字串開頭的路徑,是的話就忽略壓縮該資源文件。
判斷到最後else 就是說明該資源文件不須要壓縮了,最後是返回$minObject,$needCompress,$state,$extension這四個變量。
-------------------------------調用$this->getObjectInfo() 講解end-------------------------------------------------------------
到這裏繼續回來看 compressRes()方法裏面的代碼
//壓縮文件的絕對路徑 $minFilePath = str_replace('\\', '/', $this->resRootDir. $minObject); //************此處p判斷是最重要部分之一*****************// //判斷文件是否存在且已經改動過 if (isset($resState[$object]) && $resState[$object] == $state && isset($resState[$minObject]) && file_exists($minFilePath)) { continue; }
這段代碼首先是拼接出壓縮文件的絕對路徑,
接着下面這個判斷是關鍵的部分,經過這個判斷就能夠知道該資源文件是否被改動過,若是改動過的話,就從新對該資源文件進行壓縮,假如沒改動過,就繼續處理下一個資源文件。看這裏的判斷:isset($resState[$object]) && $resState[$object] == $state,這個判斷就是判斷該文件路徑是否存在 而且文件中對應的版本號和計算出的版本號是否還一致;isset($resState[$minObject]) && file_exists($minFilePath),這個是判斷壓縮文件路徑是否存在,而且該壓縮文件是否真實存在目錄中。
看下一段代碼,若是能走到這一部分,說明目前的這個資源文件是被改動過的(代碼修改過),那麼此時就對文件進行壓縮操做了
//確保/www/min/目錄可寫 $this->_ensureWritableDir(dirname($minFilePath)); if ($needCompress) { $this->compressResFileAndSave($filePath, $minFilePath); } else { copy($filePath, $minFilePath); }
$this->_ensureWritableDir(),此方法是要保證新建立的www/min目錄是可寫的,這裏附上代碼:
-------------------------------調用$this->_ensureWritableDir() 講解start-------------------------------------------------------------
/** * 確保目錄可寫 * @param [type] $dir [description] * @return [type] [description] */ private function _ensureWritableDir($dir) { if (!file_exists($dir)) { @mkdir($dir, 0777, true); @chmod($dir, 0777); } else if (!is_writable($dir)) { @chmod($dir, 0777); if (!is_writable($dir)) { show_error('目錄'.$dir.'不可寫'); } } }
-------------------------------調用$this->_ensureWritableDir() 講解end-------------------------------------------------------------
if ($needCompress),這個判斷資源文件是否須要壓縮,須要的話調用$this->compressResFileAndSave($filePath, $minFilePath);不須要的話,直接複製文件到壓縮文件路徑 copy($filePath, $minFilePath);
先看$this->compressResFileAndSave()
-------------------------------調用$this->compressResFileAndSave() 講解start-------------------------------------------------------------
/** * 將壓縮後的資源文件寫入到/www/min/下去 * @param [type] $filePath [description] * @param [type] $minFilePath [description] * @return [type] [description] */ private function compressResFileAndSave($filePath, $minFilePath) { if (!file_put_contents($minFilePath, $this->compressResFile($filePath))) { //$CI->exceptions->show_exception("寫入文件{$minFilePath}失敗"); show_error("寫入文件{$minFilePath}失敗", -1); } } /** * 壓縮資源文件 * @param [type] $filePath [description] * @return [type] [description] */ private function compressResFile($filePath) { $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); if ($extension === 'js') { require_once 'JShrink/Minifier.php'; return \JShrink\Minifier::minify(file_get_contents($filePath)); } else if ($extension ==='css') { $content = file_get_contents($filePath); $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content); $content = str_replace(["\r\n", "\r", "\n"], '', $content); $content = preg_replace('/([{}),;:>])\s+/', '$1', $content); $content = preg_replace('/\s+([{}),;:>])/', '$1', $content); $content = str_replace(';}', '}', $content); return $content; } else { //$CI->exceptions->show_exception("不支持壓縮{extension}文件[$filePath]"); show_error("不支持壓縮{extension}文件[$filePath]", -1); } }
先壓縮,再將壓縮後的內容寫入到 壓縮文件路徑裏去。
咱們先看下這個壓縮方法:
$this->compressResFile($filePath); 此方法中分兩類壓縮,第一類時對js文件進行壓縮,第二類的對css文件進行壓縮。先說js壓縮,這裏是調用一個JShrink的類,它
一個用來壓縮js文件的PHP類,百度能夠找到,調用這個類的minify()這個方法就能夠壓縮了;而css的壓縮利用正則替換來壓縮,把那些空格換行什麼的都去掉。到此就壓縮成功
了,而後再將壓縮後的資源寫入到對應的壓縮文件路徑裏去。
-------------------------------調用$this->compressResFileAndSave() 講解end-------------------------------------------------------------
接着繼續看compressRes()這個方法裏的代碼,這裏開始就是保存新的版本號到$resState數組裏 $object=>$state,還有就是新的壓縮路徑$minObject,而這裏$count++的做用是,當這個循環50次就將 $resState這個數組寫入一次到 resState.php文件裏,這裏是出於嚴謹考慮而已,若是你不加這個 $count的處理這部分也能夠,最後寫入一次就好了。
$resState[$object] = $state; $resState[$minObject] = ''; $count++; if ($count == 50) { $this->_saveResState($resState); $count = 0; } } } if($count) $this->_saveResState($resState);
這裏看$this->_saveResState($resState),這個方法就是將$resState數組寫入到resState.php文件裏去的方法。
-------------------------------調用$this->_saveResState($resState) 講解start-------------------------------------------------------------
private function _saveResState($resState) { ksort($resState); $content = "<?php\n\n\$resState = array(\n"; foreach ($resState as $k => $v) { $content .= "\t '$k' => '$v',\n"; } $content .= ");\n\n"; file_put_contents($this->resStatePath, $content); }
-------------------------------調用$this->_saveResState($resState) 講解end-------------------------------------------------------------
處理完後,看看所生成的文件,這裏一個文件會有多個版本,舊版本沒有刪除掉,在開發環境下刪不刪除都沒問題,這裏爲什麼不刪除舊版本的壓縮文件,這就涉及到在更新多個應用服務器代碼時所要注意的問題裏。在此我就多講解一點吧,簡單地舉個例子吧,通常大型項目中的靜態資源和模板文件是部署在不一樣的機器集羣上的,上線的過程當中,靜態資源和頁面文件的部署時間間隔可能會很是長,對於一個大型互聯網應用來講即便在一個很小的時間間隔內,都有可能出現新用戶訪問,假如舊版本的靜態資源刪除了,但新版本的靜態資源還沒部署完成,那麼用戶就加載不到該靜態資源,結果可想而知,因此,通常狀況下咱們會保留舊版本的靜態資源,而後等全部一些部署完成了,再經過必定的腳本刪除掉也不要緊,其實,這些沒必要刪除也是能夠的,你想一想,一個項目發一次版本,纔會調用一次資源文件壓縮方法,它只會對修改過的文件進行生成新版本號的靜態文件而已。這些就看我的的作法了。
咱們能夠打開看看,下面這個就是壓縮後的文件的代碼了,文件原大小爲16K,壓縮後大概少了5K,如今是11K,壓縮比大概是2/3,假如在大型項目中,一個複雜點的頁面會有很大的靜態資源文件要加載,經過此方法,大大地提升了加載的速度。(可能有些朋友以爲壓縮個幾K或者十幾K算什麼,徹底能夠忽略,其實我想說的是,當你在大型項目中優化項目的時候,可以減小几K的代碼,也給網站的性能提升了一大截)
到此,資源壓縮處理就分析完畢了。其實,有必定基礎的朋友,能夠直接看我分享的那個代碼就能夠了,假如理解不了,再看我上面這一步步的分析講解,我是處於能看來到此博客的朋友,不管技術是好或者是稍弱,都能看懂,因此纔對代碼一步步地進行分析講解。(但願各位多多支持小弟)
-------------------------------------------------------------------------------------------------------------------------
3. 接下來就是講解如何替換壓縮後的資源文件了。
這個到Home.php
1 <?php 2 defined('BASEPATH') OR exit('No direct script access allowed'); 3 4 class Home extends MY_Controller { 5 public function index() { 6 $this->smartyData['test'] = 111; 7 //這個默認是加載 www/css/home/index.css文件 8 $this->addResLink('index.css'); 9 //這個默認是加載www/js/jquery.all.min.js文件 10 $this->addResLink('/jquery.all.min.js'); 11 //這個默認是加載www/js/index.js文件 12 $this->addResLink('index.js'); 13 $this->displayView('home/index.tpl'); 14 } 15 }
上面有加載三個資源文件,咱們先看看$this->addResLink();這個方法,這個方法放在My_Controller.php裏:
/** * 資源路徑 * @param [type] $filePath [description] */ protected function addResLink($filePath) { list($filePath, $query) = explode('?', $filePath . '?'); $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); foreach ($this->_resLink as $v) { if (false === array_search($filePath, $this->_resLink[$extension])) { $this->_resLink[$extension][] = $query == null ? $filePath : $filePath .'?'. $query; } } return $this; }
這裏主要是判斷了資源文件是css仍是js,而後將其存放在 $this->_resLink這個屬性裏。
那麼此處我就先附上My_Controller.php這個父類的全部代碼吧
1 <?php 2 defined('BASEPATH') OR exit('No direct script access allowed'); 3 4 class MY_Controller extends CI_Controller { 5 public function __construct() { 6 parent::__construct(); 7 8 //壓縮jscss資源文件 9 $this->compressResHandle(); 10 } 11 12 //==========================使用SMARTY模板引擎================================// 13 /* Smarty母版頁文件路徑 */ 14 protected $masterPage = 'default.tpl'; 15 /* 視圖文件路徑*/ 16 protected $smartyView; 17 /* 要賦值給smarty視圖的數據*/ 18 protected $smartyData = []; 19 /* 資源文件*/ 20 protected $_resLink = ['js'=>[], 'css'=>[]]; 21 22 /** 23 * 使用母版頁輸出一個視圖 24 * @return [type] [description] 25 */ 26 protected function displayView($viewName = null, $masterPage = null) { 27 //爲空則選用默認母版 28 if ($masterPage == null) $masterPage = $this->masterPage; 29 //獲取視圖的輸出內容 30 $viewContent = $this->_fetchView($this->smartyData, $viewName, $masterPage); 31 32 $output = ''; 33 34 //添加css Link 35 foreach ($this->_resLink['css'] as $v) { 36 $output .= res_link($v); 37 } 38 39 //內容部分 40 $output .= $viewContent; 41 //尾部添加js 連接 42 foreach ($this->_resLink['js'] as $v) { 43 $output .= res_link($v); 44 } 45 //發送最終輸出結果以及服務器的 HTTP 頭到瀏覽器 46 47 $this->output->_display($output); 48 return $output; 49 } 50 51 private function _fetchView($smartyData, &$viewName, &$masterPage) { 52 if ($viewName == null) $viewName = $this->smartyView; 53 54 if (empty($this->smarty)) { 55 require_once SMARTY_DIR.'Smarty.class.php'; 56 $this->smarty = new Smarty(); 57 $this->smarty->setCompileDir(APPPATH . 'cache/'); 58 $this->smarty->setCacheDir(APPPATH . 'cache/'); 59 } 60 61 //設置視圖真實路徑 62 $this->_getViewDir(true, $viewName, $masterPage, $templateDir); 63 64 foreach ($smartyData as $k => $v) { 65 $this->smarty->assign($k, $v); 66 } 67 68 if (empty($masterPage)) { 69 return $this->smarty->fetch($viewName); 70 } else { 71 $this->smarty->assign('VIEW_MAIN', $viewName); 72 return $this->smarty->fetch($masterPage); 73 } 74 } 75 76 /** 77 * 資源路徑 78 * @param [type] $filePath [description] 79 */ 80 protected function addResLink($filePath) { 81 list($filePath, $query) = explode('?', $filePath . '?'); 82 $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); 83 foreach ($this->_resLink as $v) { 84 if (false === array_search($filePath, $this->_resLink[$extension])) { 85 $this->_resLink[$extension][] = $query == null ? $filePath : $filePath .'?'. $query; 86 } 87 } 88 89 return $this; 90 } 91 92 private function _getViewDir($setTemplateDir, &$viewName, &$masterPage = null, &$templateDir) { 93 if ('/' === $viewName[0]) $viewName = substr($viewName, 1); 94 95 //是否使用模板,有,則路由到 /views/master_page/*****.tpl下去 96 if ($masterPage) { 97 $masterPage = '/' === $masterPage[0] ? substr($masterPage, 1) : ('master_page' .'/'. $masterPage); 98 } 99 100 //是否設置模板目錄 101 if ($setTemplateDir) { 102 $templateDir = VIEWPATH; 103 $this->smarty->setTemplateDir($templateDir); 104 } 105 } 106 107 /** 108 * 壓縮js、css資源文件(優化) 109 * @return [type] [description] 110 */ 111 private function compressResHandle() { 112 $this->load->library('ResMinifier'); 113 //壓縮指定文件夾下的資源文件 114 $this->resminifier->compressRes(); 115 } 116 }
打印出來 $this->_resLink這個屬性的結構是這樣子的:
Array ( [js] => Array ( [0] => /jquery.all.min.js [1] => index.js ) [css] => Array ( [0] => index.css ) )
再回到Home.php裏面調用 $this->displayView('home/index.tpl');
咱們看這個方法:
/** * 使用母版頁輸出一個視圖 * @return [type] [description] */ protected function displayView($viewName = null, $masterPage = null) { //爲空則選用默認母版 if ($masterPage == null) $masterPage = $this->masterPage; //獲取視圖的輸出內容 $viewContent = $this->_fetchView($this->smartyData, $viewName, $masterPage); $output = ''; //添加css Link foreach ($this->_resLink['css'] as $v) { $output .= res_link($v); } //內容部分 $output .= $viewContent; //尾部添加js 連接 foreach ($this->_resLink['js'] as $v) { $output .= res_link($v); } //發送最終輸出結果以及服務器的 HTTP 頭到瀏覽器 $this->output->_display($output); return $output; } private function _fetchView($smartyData, &$viewName, &$masterPage) { if ($viewName == null) $viewName = $this->smartyView; if (empty($this->smarty)) { require_once SMARTY_DIR.'Smarty.class.php'; $this->smarty = new Smarty(); $this->smarty->setCompileDir(APPPATH . 'cache/'); $this->smarty->setCacheDir(APPPATH . 'cache/'); } //設置視圖真實路徑 $this->_getViewDir(true, $viewName, $masterPage, $templateDir); foreach ($smartyData as $k => $v) { $this->smarty->assign($k, $v); } if (empty($masterPage)) { return $this->smarty->fetch($viewName); } else { $this->smarty->assign('VIEW_MAIN', $viewName); return $this->smarty->fetch($masterPage); } }
這一段代碼沒有一部分就是調用了Smarty模板引擎的內容,這個有關Smarty的知識我就不講了,你們能夠本身百度,這裏主要講 res_link() 這個函數,就是經過這個函數來進行資源文件替換的。先看這個函數的代碼:
/** * 輸出 HttpHead 中的資源鏈接。 css/js 自動判斷真實路徑 * @param string 文件路徑 * @return string */ function res_link($file) { $file = res_path($file, $extension); if ($extension === 'css') { return '<link rel="stylesheet" type="text/css" href="' . $file . '"/>'; } else if ($extension === 'js') { return '<script type="text/javascript" src="'.$file.'"></script>'; } else { return false; } }
此處最重要就是 res_path() 函數了,這個函數能自動路由資源的真實路徑 。例如:index.css = > css/home/index.css
該函數最重要的一個功能是替換資源的壓縮版本。
直接看代碼:
/** * 智能路由資源真實路徑 * @param string 路徑 * @param string 擴展名 * @return string 真實路徑 */ function res_path($file, &$extension) { //檢查是否存在查詢字符串 list($file, $query) = explode('?', $file . '?'); //取得擴展名 $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); // $file = str_replace('\\', '/', $file); //取得當前控制器名 global $class; if ($class == null) exit('can not get class name'); $className = strtolower($class); //此處的規則是這樣: //例如,若是不加 / ,Home控制器對應的格式是: index.css,那麼 此處的路徑會變成css/home/index.css //假若有 / ,控制器的格式能夠是 /main.css,那麼此處的路徑會變成 css/main.css(公用的css類) if ('/' !== $file[0]) { //index.css => css/home/index.css $object = $extension .'/'. $className .'/' . $file; } else { // /css/main.css 或者 /main.css => css/main.css $object = substr($file, 1); //若object是 main.css ,則自動加上 擴展名目錄 => css/main.css if (0 !== strncasecmp($extension, $object, strlen($extension))) { $object = $extension . '/' . $object; } } //資源真實路徑 $filepath = WEBROOT.'www/'.$object; //替換壓縮版本,這部分邏輯與文件壓縮邏輯對應 if (in_array($extension, array('css', 'js'))) { if(!str_start_with($object, 'min/') && file_exists(APPPATH.'libraries/ResMinifier.php')) { require_once APPPATH.'libraries/ResMinifier.php'; $resminifier = new ResMinifier(); //獲取存放資源版本的文件的數組變量 $resState = $resminifier->getResState(); //計算獲得當前文件版本號 $state = $resminifier->_getResStateVersion($filepath); //判斷該版本號是否存在 if (isset($resState[$object])) { //判斷是不是.min.css或.min.js結尾 if (str_end_with($object, '.min.'.$extension)) { //將版本號拼接上去,而後獲得min的文件路徑 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state . '.' . $extension; } else { //將版本號拼接上去,而後獲得min的文件路徑 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension; } //判斷min的路徑是否存在在$resState裏面 if (isset($resState[$minObject])) { $object = $minObject; $query = ''; } } } $file = RES_BASE_URL . $object; } return ($query == null) ? $file : ($file .'?'. $query); }
代碼基本上都給了註釋,方便你們容易去理解,前面一部分是智能路徑css、js資源的路徑,後面一部分是替換壓縮版本,這一部分的邏輯其實和資源壓縮那裏的邏輯基本同樣,就是經過資源文件路徑,進行判斷和處理,最後獲得資源的壓縮版本的路徑,最後就將資源的壓縮版本的路徑返回去,放在'<link rel="stylesheet" type="text/css" href="' . $file . '"/>'裏面。這樣 ,就成功地將資源文件路徑替換成了壓縮版本的資源文件路徑,而且在模板輸出時,輸出的是壓縮後的資源文件。
到此,資源替換的內容就到此講解完畢。而整一項技術也分析到此。
3、總結
在這裏我集中地附上本博文講解中的幾個文件代碼:
Home.php
1 <?php 2 defined('BASEPATH') OR exit('No direct script access allowed'); 3 4 class Home extends MY_Controller { 5 public function index() { 6 $this->smartyData['test'] = 111; 7 //這個默認是加載 www/css/home/index.css文件 8 $this->addResLink('index.css'); 9 //這個默認是加載www/js/jquery.all.min.js文件 10 $this->addResLink('/jquery.all.min.js'); 11 //這個默認是加載www/js/index.js文件 12 $this->addResLink('index.js'); 13 $this->displayView('home/index.tpl'); 14 } 15 }
My_Controller.php
1 <?php 2 defined('BASEPATH') OR exit('No direct script access allowed'); 3 4 class MY_Controller extends CI_Controller { 5 public function __construct() { 6 parent::__construct(); 7 8 //壓縮jscss資源文件 9 $this->compressResHandle(); 10 } 11 12 //==========================使用SMARTY模板引擎================================// 13 /* Smarty母版頁文件路徑 */ 14 protected $masterPage = 'default.tpl'; 15 /* 視圖文件路徑*/ 16 protected $smartyView; 17 /* 要賦值給smarty視圖的數據*/ 18 protected $smartyData = []; 19 /* 資源文件*/ 20 protected $_resLink = ['js'=>[], 'css'=>[]]; 21 22 /** 23 * 使用母版頁輸出一個視圖 24 * @return [type] [description] 25 */ 26 protected function displayView($viewName = null, $masterPage = null) { 27 //爲空則選用默認母版 28 if ($masterPage == null) $masterPage = $this->masterPage; 29 //獲取視圖的輸出內容 30 $viewContent = $this->_fetchView($this->smartyData, $viewName, $masterPage); 31 32 $output = ''; 33 34 //添加css Link 35 foreach ($this->_resLink['css'] as $v) { 36 $output .= res_link($v); 37 } 38 39 //內容部分 40 $output .= $viewContent; 41 //尾部添加js 連接 42 foreach ($this->_resLink['js'] as $v) { 43 $output .= res_link($v); 44 } 45 //發送最終輸出結果以及服務器的 HTTP 頭到瀏覽器 46 47 $this->output->_display($output); 48 return $output; 49 } 50 51 private function _fetchView($smartyData, &$viewName, &$masterPage) { 52 if ($viewName == null) $viewName = $this->smartyView; 53 54 if (empty($this->smarty)) { 55 require_once SMARTY_DIR.'Smarty.class.php'; 56 $this->smarty = new Smarty(); 57 $this->smarty->setCompileDir(APPPATH . 'cache/'); 58 $this->smarty->setCacheDir(APPPATH . 'cache/'); 59 } 60 61 //設置視圖真實路徑 62 $this->_getViewDir(true, $viewName, $masterPage, $templateDir); 63 64 foreach ($smartyData as $k => $v) { 65 $this->smarty->assign($k, $v); 66 } 67 68 if (empty($masterPage)) { 69 return $this->smarty->fetch($viewName); 70 } else { 71 $this->smarty->assign('VIEW_MAIN', $viewName); 72 return $this->smarty->fetch($masterPage); 73 } 74 } 75 76 /** 77 * 資源路徑 78 * @param [type] $filePath [description] 79 */ 80 protected function addResLink($filePath) { 81 list($filePath, $query) = explode('?', $filePath . '?'); 82 $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); 83 foreach ($this->_resLink as $v) { 84 if (false === array_search($filePath, $this->_resLink[$extension])) { 85 $this->_resLink[$extension][] = $query == null ? $filePath : $filePath .'?'. $query; 86 } 87 } 88 89 return $this; 90 } 91 92 private function _getViewDir($setTemplateDir, &$viewName, &$masterPage = null, &$templateDir) { 93 if ('/' === $viewName[0]) $viewName = substr($viewName, 1); 94 95 //是否使用模板,有,則路由到 /views/master_page/*****.tpl下去 96 if ($masterPage) { 97 $masterPage = '/' === $masterPage[0] ? substr($masterPage, 1) : ('master_page' .'/'. $masterPage); 98 } 99 100 //是否設置模板目錄 101 if ($setTemplateDir) { 102 $templateDir = VIEWPATH; 103 $this->smarty->setTemplateDir($templateDir); 104 } 105 } 106 107 /** 108 * 壓縮js、css資源文件(優化) 109 * @return [type] [description] 110 */ 111 private function compressResHandle() { 112 $this->load->library('ResMinifier'); 113 //壓縮指定文件夾下的資源文件 114 $this->resminifier->compressRes(); 115 } 116 }
ResMinifier.php
1 <?php 2 defined('BASEPATH') OR exit('No direct script access allowed'); 3 /** 4 * 資源壓縮類 5 */ 6 class ResMinifier { 7 /** 須要壓縮的資源目錄*/ 8 public $compressResDir = ['css', 'js']; 9 /** 忽略壓縮的路徑,例如此處是js/icon開頭的路徑忽略壓縮*/ 10 public $compressResIngorePrefix = ['js/icon']; 11 /** 資源根目錄*/ 12 public $resRootDir; 13 /** 資源版本文件路徑*/ 14 private $resStatePath; 15 16 public function __construct() { 17 $this->resRootDir = WEBROOT . 'www/'; 18 $this->resStatePath = WEBROOT . 'www/resState.php'; 19 } 20 21 public function compressRes() { 22 //獲取存放版本的資源文件 23 $resState = $this->getResState(); 24 $count = 0; 25 26 //開始遍歷須要壓縮的資源目錄 27 foreach ($this->compressResDir as $resDir) { 28 foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->resRootDir . $resDir , FilesystemIterator::SKIP_DOTS)) as $file) { 29 //獲取該資源文件的絕對路徑 30 $filePath = str_replace('\\', '/', $file->getRealPath()); 31 32 //獲取文件相對路徑 33 $object = substr($filePath, strlen($this->resRootDir)); 34 35 //計算文件的版本號 36 $state = $this->_getResStateVersion($filePath); 37 38 //獲取文件的幾個參數值 39 if (true !== $this->getObjectInfo($object, $minObject, $needCompress, $state, $extension)) { 40 continue; 41 } 42 43 //壓縮文件的絕對路徑 44 $minFilePath = str_replace('\\', '/', $this->resRootDir. $minObject); 45 46 //************此處p判斷是最重要部分之一*****************// 47 //判斷文件是否存在且已經改動過 48 if (isset($resState[$object]) && $resState[$object] == $state && isset($resState[$minObject]) && file_exists($minFilePath)) { 49 continue; 50 } 51 52 //確保/www/min/目錄可寫 53 $this->_ensureWritableDir(dirname($minFilePath)); 54 55 if ($needCompress) { 56 $this->compressResFileAndSave($filePath, $minFilePath); 57 } else { 58 copy($filePath, $minFilePath); 59 } 60 61 62 $resState[$object] = $state; 63 $resState[$minObject] = ''; 64 $count++; 65 66 if ($count == 50) { 67 $this->_saveResState($resState); 68 $count = 0; 69 } 70 71 } 72 } 73 if($count) $this->_saveResState($resState); 74 } 75 76 /** 77 * 獲取資源文件相關信息 78 * @param [type] $object 資源文件路徑 (www/css/home/index.css) 79 * @param [type] $minObject 壓縮資源文件路徑 (www/min/css/home/index.ae123a.css) 80 * @param [type] $needCompress 是否須要壓縮 81 * @param [type] $state 文件版本號 82 * @param [type] $extension 文件名後綴 83 * @return [type] [description] 84 */ 85 public function getObjectInfo($object, &$minObject, &$needCompress, &$state, &$extension) { 86 //獲取資源絕對路徑 87 $filePath = $this->resRootDir . $object; 88 //判斷資源是否存在 89 if (!file_exists($filePath)) return "資源文件不存在{$filePath}"; 90 //版本號 91 $state = $this-> _getResStateVersion($filePath); 92 //文件名後綴 93 $extension = pathinfo($filePath, PATHINFO_EXTENSION); 94 //是否要壓縮 95 $needCompress = true; 96 97 //判斷資源文件是不是以 .min.css或者.min.js結尾的 98 //此類結尾通常都是已壓縮過,例如jquery.min.js,就沒必要再壓縮了 99 if (str_end_with($object, '.min.'.$extension, true)) { 100 //壓縮後的資源存放路徑,放在 /www/min/ 目錄下 101 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state .'.'. $extension; 102 $needCompress = false; 103 } else if (in_array($extension, $this->compressResDir)) { 104 //此處是須要壓縮的文件目錄 105 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension; 106 //看看是不是忽略的路徑前綴 107 foreach ($this->compressResIngorePrefix as $v) { 108 if (str_start_with($object, $v, true)) { 109 $needCompress = false; 110 } 111 } 112 } else { 113 $minObject = 'min/'.$object; 114 $needCompress = false; 115 } 116 return true; 117 } 118 119 120 /** 121 * 獲取存放資源版本的文件 122 * 它是放在一個數組裏 123 * $resState = array( 124 * '文件路徑' => '對應的版本號', 125 * '文件路徑' => '對應的版本號', 126 * '文件路徑' => '對應的版本號', 127 * ); 128 * @return [type] [description] 129 */ 130 public function getResState() { 131 if (file_exists($this->resStatePath)) { 132 require $this->resStatePath; 133 return $resState; 134 } 135 return []; 136 } 137 138 /** 139 * 計算文件的版本號,這個是根據計算文件MD5散列值獲得版本號 140 * 只要文件內容改變了,所計算獲得的散列值就會不同 141 * 用於判斷資源文件是否有改動過 142 * @param [type] $filePath [description] 143 * @return [type] [description] 144 */ 145 public function _getResStateVersion($filePath) { 146 return base_convert(crc32(md5_file($filePath)), 10, 36); 147 } 148 149 /** 150 * 確保目錄可寫 151 * @param [type] $dir [description] 152 * @return [type] [description] 153 */ 154 private function _ensureWritableDir($dir) { 155 if (!file_exists($dir)) { 156 @mkdir($dir, 0777, true); 157 @chmod($dir, 0777); 158 } else if (!is_writable($dir)) { 159 @chmod($dir, 0777); 160 if (!is_writable($dir)) { 161 show_error('目錄'.$dir.'不可寫'); 162 } 163 } 164 } 165 166 /** 167 * 將壓縮後的資源文件寫入到/www/min/下去 168 * @param [type] $filePath [description] 169 * @param [type] $minFilePath [description] 170 * @return [type] [description] 171 */ 172 private function compressResFileAndSave($filePath, $minFilePath) { 173 if (!file_put_contents($minFilePath, $this->compressResFile($filePath))) { 174 175 //$CI->exceptions->show_exception("寫入文件{$minFilePath}失敗"); 176 show_error("寫入文件{$minFilePath}失敗", -1); 177 } 178 } 179 180 /** 181 * 壓縮資源文件 182 * @param [type] $filePath [description] 183 * @return [type] [description] 184 */ 185 private function compressResFile($filePath) { 186 $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); 187 if ($extension === 'js') { 188 require_once 'JShrink/Minifier.php'; 189 return \JShrink\Minifier::minify(file_get_contents($filePath)); 190 } else if ($extension ==='css') { 191 $content = file_get_contents($filePath); 192 $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content); 193 $content = str_replace(["\r\n", "\r", "\n"], '', $content); 194 $content = preg_replace('/([{}),;:>])\s+/', '$1', $content); 195 $content = preg_replace('/\s+([{}),;:>])/', '$1', $content); 196 $content = str_replace(';}', '}', $content); 197 return $content; 198 } else { 199 //$CI->exceptions->show_exception("不支持壓縮{extension}文件[$filePath]"); 200 show_error("不支持壓縮{extension}文件[$filePath]", -1); 201 202 } 203 } 204 205 private function _saveResState($resState) { 206 ksort($resState); 207 $content = "<?php\n\n\$resState = array(\n"; 208 foreach ($resState as $k => $v) { 209 $content .= "\t '$k' => '$v',\n"; 210 } 211 $content .= ");\n\n"; 212 file_put_contents($this->resStatePath, $content); 213 } 214 215 }
Common.php
1 <?php 2 /** 3 * 輸出 HttpHead 中的資源鏈接。 css/js 自動判斷真實路徑 4 * @param string 文件路徑 5 * @return string 6 */ 7 function res_link($file) { 8 $file = res_path($file, $extension); 9 10 if ($extension === 'css') { 11 return '<link rel="stylesheet" type="text/css" href="' . $file . '"/>'; 12 } else if ($extension === 'js') { 13 return '<script type="text/javascript" src="'.$file.'"></script>'; 14 } else { 15 return false; 16 } 17 } 18 19 /** 20 * 智能路由資源真實路徑 21 * @param string 路徑 22 * @param string 擴展名 23 * @return string 真實路徑 24 */ 25 function res_path($file, &$extension) { 26 //檢查是否存在查詢字符串 27 list($file, $query) = explode('?', $file . '?'); 28 //取得擴展名 29 $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); 30 // 31 $file = str_replace('\\', '/', $file); 32 //取得當前控制器名 33 global $class; 34 if ($class == null) exit('can not get class name'); 35 $className = strtolower($class); 36 37 //此處的規則是這樣: 38 //例如,若是不加 / ,Home控制器對應的格式是: index.css,那麼 此處的路徑會變成css/home/index.css 39 //假若有 / ,控制器的格式能夠是 /main.css,那麼此處的路徑會變成 css/main.css(公用的css類) 40 if ('/' !== $file[0]) { 41 //index.css => css/home/index.css 42 $object = $extension .'/'. $className .'/' . $file; 43 } else { 44 // /css/main.css 或者 /main.css => css/main.css 45 $object = substr($file, 1); 46 47 //若object是 main.css ,則自動加上 擴展名目錄 => css/main.css 48 if (0 !== strncasecmp($extension, $object, strlen($extension))) { 49 $object = $extension . '/' . $object; 50 } 51 } 52 //資源真實路徑 53 $filepath = WEBROOT.'www/'.$object; 54 55 //替換壓縮版本,這部分邏輯與文件壓縮邏輯對應 56 if (in_array($extension, array('css', 'js'))) { 57 if(!str_start_with($object, 'min/') && file_exists(APPPATH.'libraries/ResMinifier.php')) { 58 require_once APPPATH.'libraries/ResMinifier.php'; 59 $resminifier = new ResMinifier(); 60 //獲取存放資源版本的文件的數組變量 61 $resState = $resminifier->getResState(); 62 //計算獲得當前文件版本號 63 $state = $resminifier->_getResStateVersion($filepath); 64 //判斷該版本號是否存在 65 if (isset($resState[$object])) { 66 //判斷是不是.min.css或.min.js結尾 67 if (str_end_with($object, '.min.'.$extension)) { 68 //將版本號拼接上去,而後獲得min的文件路徑 69 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state . '.' . $extension; 70 } else { 71 //將版本號拼接上去,而後獲得min的文件路徑 72 $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension; 73 } 74 //判斷min的路徑是否存在在$resState裏面 75 if (isset($resState[$minObject])) { 76 $object = $minObject; 77 $query = ''; 78 } 79 } 80 81 } 82 83 $file = RES_BASE_URL . $object; 84 } 85 86 return ($query == null) ? $file : ($file .'?'. $query); 87 88 } 89 90 /** 91 * 判斷 subject 是否以 search開頭, 參數指定是否忽略大小寫 92 * @param [type] $subject [description] 93 * @param [type] $search [description] 94 * @param boolean $ignore_case [description] 95 * @return [type] [description] 96 */ 97 function str_start_with($subject, $search, $ignore_case = false) { 98 $len2 = strlen($search); 99 if (0 === $len2) return true; 100 $len1 = strlen($subject); 101 if ($len1 < $len2) return false; 102 if ($ignore_case) { 103 return 0 === strncmp($subject, $search, $len2); 104 } else { 105 return 0 === strncasecmp($subject, $search, $len2); 106 } 107 } 108 109 /** 110 * 判斷 subject 是否以 search結尾, 參數指定是否忽略大小寫 111 * @param [type] $subject [description] 112 * @param [type] $search [description] 113 * @param boolean $ignore_case [description] 114 * @return [type] [description] 115 */ 116 function str_end_with($subject, $search, $ignore_case = false) { 117 $len2 = strlen($search); 118 if (0 === $len2) return true; 119 $len1 = strlen($subject); 120 if ($len2 > $len1) return false; 121 if ($ignore_case) { 122 return 0 === strcmp(substr($subject, $len1 - $len2), $search); 123 } else { 124 return 0 === strcasecmp(substr($subject, $len1 - $len2), $search); 125 } 126 }
$resState.php(裏面的代碼是自動生成的)
1 <?php 2 3 $resState = array( 4 'css/home/index.css' => 'gwy933', 5 'js/echarts-all.min.js' => 'wqrf1c', 6 'js/home/index.js' => 's2z6f5', 7 'js/icon.js' => 'pgcyih', 8 'js/icon_home.js' => 'zhl9iu', 9 'js/ion.rangeSlider.min.js' => 'akq381', 10 'js/jquery-ui-autocomplete.js' => '8nzacv', 11 'js/jquery-ui.min.js' => 'i6tw8z', 12 'js/jquery.all.min.js' => 'd2w76v', 13 'js/jquery.city.js' => 'toxdrf', 14 'js/jquery.easydropdown.min.js' => '2ni3i0', 15 'js/jquery.matrix.js' => '3vrqkk', 16 'js/jquery.mobile.all.min.js' => 'ernu7r', 17 'js/jquery.qrcode.min.js' => 'yuhnsj', 18 'js/jquery.tinyscrollbar.min.js' => 'oakk3c', 19 'js/mobiscroll.custom.min.js' => 'kn8h2e', 20 'js/store.min.js' => 'n50jwr', 21 'js/swiper.animate1.0.2.min.js' => 'mm27zc', 22 'js/swiper.min.js' => 'jicwhh', 23 'min/css/home/index.6a4e83eb.css' => '', 24 'min/css/home/index.gwy933.css' => '', 25 'min/css/home/index.puzbnf.css' => '', 26 'min/css/home/index.thv8x7.css' => '', 27 'min/js/echarts-all.76025ee0.js' => '', 28 'min/js/echarts-all.wqrf1c.js' => '', 29 'min/js/home/index.65363d41.js' => '', 30 'min/js/home/index.s2z6f5.js' => '', 31 'min/js/icon.5bbd4db9.js' => '', 32 'min/js/icon.pgcyih.js' => '', 33 'min/js/icon_home.7fe74076.js' => '', 34 'min/js/icon_home.zhl9iu.js' => '', 35 'min/js/ion.rangeSlider.261d8ed1.js' => '', 36 'min/js/ion.rangeSlider.akq381.js' => '', 37 'min/js/jquery-ui-autocomplete.1f3bb62f.js' => '', 38 'min/js/jquery-ui-autocomplete.8nzacv.js' => '', 39 'min/js/jquery-ui.418e9683.js' => '', 40 'min/js/jquery-ui.i6tw8z.js' => '', 41 'min/js/jquery.all.2f248267.js' => '', 42 'min/js/jquery.all.d2w76v.js' => '', 43 'min/js/jquery.city.6b036feb.js' => '', 44 'min/js/jquery.city.toxdrf.js' => '', 45 'min/js/jquery.easydropdown.2ni3i0.js' => '', 46 'min/js/jquery.easydropdown.98fa138.js' => '', 47 'min/js/jquery.matrix.3vrqkk.js' => '', 48 'min/js/jquery.matrix.dfe2a44.js' => '', 49 'min/js/jquery.mobile.all.3539ebb7.js' => '', 50 'min/js/jquery.mobile.all.ernu7r.js' => '', 51 'min/js/jquery.qrcode.7d9738b3.js' => '', 52 'min/js/jquery.qrcode.yuhnsj.js' => '', 53 'min/js/jquery.tinyscrollbar.578e4cb8.js' => '', 54 'min/js/jquery.tinyscrollbar.oakk3c.js' => '', 55 'min/js/mobiscroll.custom.4a684f66.js' => '', 56 'min/js/mobiscroll.custom.kn8h2e.js' => '', 57 'min/js/store.536545cb.js' => '', 58 'min/js/store.n50jwr.js' => '', 59 'min/js/swiper.4650ad75.js' => '', 60 'min/js/swiper.animate1.0.2.517f82e8.js' => '', 61 'min/js/swiper.animate1.0.2.mm27zc.js' => '', 62 'min/js/swiper.jicwhh.js' => '', 63 );
另外附上JShrink這個PHP類的連接給你們下載 http://pan.baidu.com/s/1gd12JT5
要是你們仍是以爲不夠OK的話,我直接將這個實驗項目打包供你們下載下來學習和了解:http://pan.baidu.com/s/1o6OUYPO
4、結語
最後我來分享咱們線上項目的具體實現方案:
咱們的項目分線上環境、開發環境和測試環境,在開發和測試環境中,咱們每一次訪問都會調用壓縮文件的接口,而後再對生成的資源文件的大小是要作判斷的,若是壓縮後文件太小,就要求將該資源文件的代碼合併到其餘資源文件裏去,以此減小沒必要要的HTTP請求(由於文件過小,資源的下載時間遠遠小於HTTP請求響應所消耗的時間);另外一個是圖片的處理,全部圖片都要通過壓縮才能經過(例如在:https://tinypng.com/ 這個網站去壓縮圖片),在PC端,若是是小圖標的話,使用圖片合併的方式進行優化,詳情可參考本人的這篇博文:http://www.cnblogs.com/it-cen/p/4618954.html 而在wap端的圖片處理採用的是base64編碼方式來處理圖片,詳情能夠參考本人的這篇博文:http://www.cnblogs.com/it-cen/p/4624939.html ,當頁面輸出時,會使用redis來緩存頁面(爲啥用內存來緩存而不是採用頁面緩存,這個之後再分享給你們)。若是是線上環境,每發一次版本,纔會調用一下資源文件壓縮這個接口,而且線上的靜態資源(css、js、圖片)是存放在阿里雲的OSS裏的,與咱們的應用服務器是分開的。這是咱們線上項目的一部分優化解決方案,固然了,還有更多優化技術,我會在之後一一總結和分享出來,方便你們一塊兒學習和交流。
本次博文就分享到此,謝謝閱覽此博文的朋友們。
若是此博文中有哪裏講得讓人難以理解,歡迎留言交流,如有講解錯的地方歡迎指出。
若是您以爲您能在此博文學到了新知識,請爲我頂一個,如文章中有解釋錯的地方,歡迎指出。
互相學習,共同進步!