Automatic JavaScript and CSS Packer(转)
原文地址:http://bakery.cakephp.org/articles/view/automatic-javascript-and-css-packer
Notes
- JavaScript packing only works with PHP5. If you’re using PHP4 the scripts will still be merged into one file, but not packed.
- A sample .htaccess file is included in the zip. Rename it and drop it in js/packed and css/packed for improved performance.
Download
A zip of the code and .htaccess can be found here: http://github.com/mcurry/cakephp/tree/master/helpers/asset, as well as posted below.
Instructions
- You’ll need a working version of CakePHP 1.2 installed.
- Download jsmin 1.1.0 (http://code.google.com/p/jsmin-php/) or later and put it in vendors/jsmin.
- Download CSSTidy 1.3 (http://csstidy.sourceforge.net/) or later and put the contents in vendors/csstidy.
- Download/copy+paste the helper and unzip it to app/views/helpers.
- Include the helper in any controller that will need it. Most likely you will put it in AppController so that it’s available to all your controllers:
Tips
- Remember to set the inline option to false for JS and CSS in your layout if you want them to be packed with the view scripts.
- Setting DEBUG on will cause this helper to output the scripts the same way $scripts_for_layout would, effectifly turning it off while testing.
- If you get a JavaScript error with a packed version of a file it’s most likely missing a semi-colon somewhere.
- Order is important. If you include script1 then script2 on one view and script2 then script1 on another, they will generate separate packed versions and will be treated by the browser as separate scripts.
Helper Class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | <?php /* * Asset Packer CakePHP Component * Copyright (c) 2008 Matt Curry * www.PseudoCoder.com * http://www.pseudocoder.com/archives/2007/08/08/automatic-asset-packer-cakephp-helper * * @author mattc <matt@pseudocoder.com> * @version 1.2 * @license MIT * */ class AssetHelper extends Helper { //Cake debug = 0 packed js/css returned. $this->debug doesn't do anything. //Cake debug > 0, $this->debug = false essentially turns the helper off. js/css not packed. Good for debugging your js/css files. //Cake debug > 0, $this->debug = true packed js/css returned. Good for debugging this helper. var $debug = false; //there is a *minimal* perfomance hit associated with looking up the filemtimes //if you clean out your cached dir (as set below) on builds then you don't need this. var $checkTS = true; //the packed files are named by stringing together all the individual file names //this can generate really long names, so by setting this option to true //the long name is md5'd, producing a resonable length file name. var $md5FileName = false; //you can change this if you want to store the files in a different location. //this is relative to your webroot/js and webroot/css paths var $cachePath = 'packed/'; //set the css compression level //options: default, low_compression, high_compression, highest_compression //default is no compression //I like high_compression because it still leaves the file readable. var $cssCompression = 'high_compression'; var $helpers = array('Html', 'Javascript'); var $viewScriptCount = 0; //flag so we know the view is done rendering and it's the layouts turn function afterRender() { $view =& ClassRegistry::getObject('view'); $this->viewScriptCount = count($view->__scripts); } function scripts_for_layout() { $view =& ClassRegistry::getObject('view'); //nothing to do if (!$view->__scripts) { return; } //move the layout scripts to the front $view->__scripts = array_merge( array_slice($view->__scripts, $this->viewScriptCount), array_slice($view->__scripts, 0, $this->viewScriptCount) ); if (Configure::read('debug') && $this->debug == false) { return join("\n\t", $view->__scripts); } //split the scripts into js and css foreach ($view->__scripts as $i => $script) { if (preg_match('/js\/(.*).js/', $script, $match)) { $temp = array(); $temp['script'] = $match[1]; $temp['name'] = basename($match[1]); $js[] = $temp; //remove the script since it will become part of the merged script unset($view->__scripts[$i]); } else if (preg_match('/css\/(.*).css/', $script, $match)) { $temp = array(); $temp['script'] = $match[1]; $temp['name'] = basename($match[1]); $css[] = $temp; //remove the script since it will become part of the merged script unset($view->__scripts[$i]); } } $scripts_for_layout = ''; //first the css if (!empty($css)) { $scripts_for_layout .= $this->Html->css($this->cachePath . $this->process('css', $css)); $scripts_for_layout .= "\n\t"; } //then the js if (!empty($js)) { $scripts_for_layout .= $this->Javascript->link($this->cachePath . $this->process('js', $js)); } //finally anything that was left over, usually codeBlocks $scripts_for_layout .= join("\n\t", $view->__scripts); return $scripts_for_layout; } function process($type, $data) { switch ($type) { case 'js': $path = JS; break; case 'css': $path = CSS; break; } $folder = new Folder; //make sure the cache folder exists if ($folder->create($path . $this->cachePath, "777")) { trigger_error('Could not create ' . $path . $this->cachePath . '. Please create it manually with 777 permissions', E_USER_WARNING); } //check if the cached file exists $names = Set::extract($data, '{n}.name'); $folder->cd($path . $this->cachePath); $fileName = $folder->find($this->__generateFileName($names) . '_([0-9]{10}).' . $type); if ($fileName) { //take the first file...really should only be one. $fileName = $fileName[0]; } //make sure all the pieces that went into the packed script //are OLDER then the packed version if ($this->checkTS && $fileName) { $packed_ts = filemtime($path . $this->cachePath . $fileName); $latest_ts = 0; $scripts = Set::extract($data, '{n}.script'); foreach($scripts as $script) { $latest_ts = max($latest_ts, filemtime($path . $script . '.' . $type)); } //an original file is newer. need to rebuild if ($latest_ts > $packed_ts) { unlink($path . $this->cachePath . $fileName); $fileName = null; } } //file doesn't exist. create it. if (!$fileName) { $ts = time(); //merge the script $scriptBuffer = ''; $scripts = Set::extract($data, '{n}.script'); foreach($scripts as $script) { $buffer = file_get_contents($path . $script . '.' . $type); switch ($type) { case 'js': //jsmin only works with PHP5 if (PHP5) { vendor('jsmin/jsmin'); $buffer = trim(JSMin::minify($buffer)); } break; case 'css': vendor('csstidy/class.csstidy'); $tidy = new csstidy(); $tidy->load_template($this->cssCompression); $tidy->parse($buffer); $buffer = $tidy->print->plain(); break; } $scriptBuffer .= "\n/* $script.$type */\n" . $buffer; } //write the file $fileName = $this->__generateFileName($names) . '_' . $ts . '.' . $type; $file = new File($path . $this->cachePath . $fileName); $file->write(trim($scriptBuffer)); } if ($type == 'css') { //$html->css doesn't check if the file already has //the .css extension and adds it automatically, so we need to remove it. $fileName = str_replace('.css', '', $fileName); } return $fileName; } function __generateFileName($names) { $fileName = str_replace('.', '-', implode('_', $names)); if ($this->md5FileName) { $fileName = md5($fileName); } return $fileName; } } ?> |
DEBUG usage in the latest revisions and .htaccess file
Change DEBUG on line 51 to Configure::read(’debug’) as we are moving to using Configure class instead of constants :)
also i use this .htaccess file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <IfModule mod_deflate.c> # compress content with type html, text, and css AddOutputFilterByType DEFLATE text/css text/javascript application/x-javascript text/js <ifmodule mod_headers.c> # properly handle requests coming from behind proxies Header append Vary User-Agent </ifmodule> </IfModule> <IfModule mod_expires.c> ExpiresActive On ExpiresByType text/css "access plus 10 years" ExpiresByType text/js "access plus 10 years" ExpiresByType text/javascript "access plus 10 years" ExpiresByType application/x-javascript "access plus 10 years" ExpiresByType image/png "access plus 10 years" </IfModule> FileETag none |
Firstly, thanks to Matt for taking the time and effort to post such a generally useful helper. I vote to include it in the cake core.
Anyways, my app uses cake 1.2’s theming capability, and the asset helper as it currently exists (version 1.2 of the helper) doesn’t support this. In particular, it doesn’t first check the webroot/themed/ directory for the themed versions of the css/js files.
So, I made a couple of trivial modifications to the process() function in the helper.
Here it is (sorry, my comments are also included in the code – feel free to delete them).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | <?php function process($type, $data) { switch ($type) { case 'js': $path = JS; $themedPath = $this->webroot.$this->themeWeb.'js/'; break; case 'css': $path = CSS; $themedPath = WWW_ROOT.$this->themeWeb.'css/'; break; } $folder = new Folder; //make sure the cache folder 'packed/' exists if ($folder->create($path . $this->cachePath, "777")) { trigger_error('Could not create ' . $path . $this->cachePath . '. Please create it manually with 777 permissions', E_USER_WARNING); } //Does a cached packed file exist? $names = Set::extract($data, '{n}.name'); $folder->cd($path . $this->cachePath); $fileName = $folder->find($this->__generateFileName($names) . '_([0-9]{10}).' . $type); if ($fileName) { //Cached packed file exists. Take the first one...really should only be one, // because we delete them, before we create another one. $fileName = $fileName[0]; } // Make sure all the components that made up the packed file // are OLDER then the packed version if ($this->checkTS && $fileName) { // Get the filetime of the packed file. $packed_ts = filemtime($path . $this->cachePath . $fileName); // get the newest filetime of all the components. $latest_ts = 0; $scripts = Set::extract($data, '{n}.script'); foreach($scripts as $script) { if (is_file($themedPath . $script . '.' . $type)){ $latest_ts = max($latest_ts, filemtime($themedPath . $script . '.' . $type)); }else{ $latest_ts = max($latest_ts, filemtime($path . $script . '.' . $type)); } } // Are any of the components newer? If so, need to rebuild the packed file, so first delete it. if ($latest_ts > $packed_ts) { unlink($path . $this->cachePath . $fileName); $fileName = null; } } //file didn't exist, or it did, but we deleted it because it needs to be rebuilt. Build it. if (!$fileName) { $ts = time(); $scriptBuffer = ''; $scripts = Set::extract($data, '{n}.script'); foreach($scripts as $script) { // first try the themed path to see if the file exists. if (is_file($themedPath . $script . '.' . $type)){ // file exists in themed path. $buffer = file_get_contents($themedPath . $script . '.' . $type); }else{ // nothing in themed path, use the default path. $buffer = file_get_contents($path . $script . '.' . $type); } switch ($type) { case 'js': //jsmin only works with PHP5 if (PHP5) { vendor('jsmin/jsmin'); $buffer = trim(JSMin::minify($buffer)); } break; case 'css': vendor('csstidy/class.csstidy'); $tidy = new csstidy(); $tidy->load_template($this->cssCompression); $tidy->parse($buffer); $buffer = $tidy->print->plain(); break; } $scriptBuffer .= "\n/* $script.$type */\n" . $buffer; } //write the packed file to the default path. Reason we don't write to the // themed path is because some of the files may have come from the theme, but some // may be generic to all apps and may have come from the default path. // So we just write the packed file containining everything to the default path. $fileName = $this->__generateFileName($names) . '_' . $ts . '.' . $type; $file = new File($path . $this->cachePath . $fileName); $file->write(trim($scriptBuffer)); } if ($type == 'css') { //$html->css doesn't check if the file already has //the .css extension and adds it automatically, so we need to remove it. $fileName = str_replace('.css', '', $fileName); } return $fileName; } ?> |
Other
1 2 3 4 5 | $this->viewScriptCount = count($view->__scripts); # to if(isset($view->__scripts) && !empty($view->__scripts)) { $this->viewScriptCount = count($view->__scripts); } |
Monitor Your Web Site 24/7 - Receive email and SMS alerts anytime your web site goes down.
