require.js+backbone 使用r.js 來壓縮,本地不壓縮,生產環境壓縮 的實現方式

來源:互聯網
上載者:User

標籤:backbone   require   r.js   壓縮   架構   

requie.js 和backbone.js 這裡就不說了,可以去看官方文檔,都很詳細!

但是使用require.js 預設帶的壓縮方式感覺不是很方便,所以本文主要講 利用r.js壓縮,來實現本地不壓縮,生產環境壓縮

r.js 是運行在node上的,預設使用UglifyJS。UglifyJS真的很好用,那為什麼說預設的方式 不是很方便呢?

r.js 單獨壓縮一個檔案也很好用的,但在實際項目中,總不能一個一個壓吧!因此r.js提供了一種多檔案的壓縮方式

,使用一個叫bulid.js 的設定檔來配置模組,這樣可以壓縮多個模組。
但是,問題有幾個:

1.要維護一個設定檔,模組越多,越不好維護。當然也可寫個自動組建組態檔案的指令碼,個人感覺也不好用,因為第二個問題。

2.壓縮後,會產生整個檔案夾壓縮後的完整副本,這樣你就要提交兩個js的檔案夾到你的程式碼程式庫裡了。而且壓縮後的檔案夾裡存在代碼的冗餘,因為所有的代碼都會根據層層依賴關係被壓縮的一個入口檔案中,載入是只需載入入口檔案就行了,但其他的檔案也被壓縮了,被複製到了新的檔案夾內。

3.壓縮時每次都全部壓縮,效率很低!可能也能實現部分壓縮,不過我沒找到合適的方法。

4.本地使用未壓縮的,壓縮後提交,不能保證100%的壓縮正確(配置裡的依賴萬一出錯了),這樣需要提交到測試環境才能發現。

問題說完了,有能解決的歡迎回複。下面說說我的實現方式。window開發環境&&node環境&&apache需要開啟rewrite和eTag。

首先大概說下原理:
總共分兩步,1是合并;2是壓縮;

1.利用apche的.htaccess檔案將請求的入口檔案(main.js) 重新導向到一個php指令碼裡(本地環境裡)。在這個指令碼裡來判斷是否需要合并(這裡說的只是合并,不是壓縮),如果需要,利用r.js在另一個目錄合并成一個main.dev.js ,然後根據所依賴的檔案修改時間來產生eTag的token,設定eTag,並將內容輸出。如果不需要合并(通過eTag來判斷),則直接返回304,去讀之前的緩衝。.這樣本地載入的即為合并但未壓縮的js檔案,便於調試.


2.這個main.dev.js 並不需要提交的你的程式碼程式庫,只保留在本地就行。這是還需要另一個php指令碼,通過一個批處理來調用。指令碼的作用是把main.dev.js再相同的目錄壓縮出一個main.js,這裡也需要根居main.js是否存在和main.js 與main.dev.js 的檔案修改時間做比較,來判斷是否壓縮。壓縮好的main.js 即使要提交到程式碼程式庫裡的(從main.dev.js 到main.js 相當於是單個檔案壓縮,沒有依賴關係,這樣出錯的機率就很小很小了.很好的解決了上述提出的問題)。


下面來說下具體的實現方式:

1.目錄結構是這樣的:


簡單解釋下 

左邊是js的目錄結構圖,lib是放核心架構的,如jquery等;common是放入自己寫的公用模組,如彈出遮罩層等。app即應用的代碼了,main是入口檔案,product是放置合并過和壓縮過的,檔案名稱和main裡的相同。其他的就是backbone的目錄結構了,可能還有些不全面,這裡先不考慮。

右邊是壓縮指令碼的目錄,r.js 即require.js 提供的壓縮js指令碼,compile.bat 調用壓縮指令碼的批次檔,combine.php 合并代碼的PHP指令碼,conplile.php 是壓縮代碼的指令碼,notmodified.php 是判斷是否需要合并,原理是利用所依賴的檔案修改時間產生Etag的token,combine_css.php 是合并css的指令碼,講完js壓縮後再說。


2.瞭解了目錄的結構和基本原理,剩下的就是貼代碼了。

html的的引入:

<script data-main="<?=$jsServer?>js/app/product/spc" src="<?=$jsServer?>js/lib/require-2.1.14.min.js?v=<?=$ver?>"></script>
程式的入口是js/app/product/spc,這個檔案是由js/app/mian/spc.js經過合并和壓縮的到的,即在生產環境使用的。

在本地環境的話,就要靠.htaccess這個檔案了

.htaccess

# 將js/app/product/spc.js 重新導向到combine.php這個指令碼裡,並將product替換為main,# 即js/app/mian/spc.js 這個真正的入口檔案路徑作為參數傳過去rewriteCond %{REQUEST_FILENAME} ^(.*)product(.*\.js)$rewriteCond %1main%2 -fRewriteRule ^(.*)product(.*\.js)$ autocompile/combine.php?f=$1main$2 [B,L]             #css的合并,和上面的一樣,只不過對應處理的指令碼不同rewriteCond %{REQUEST_FILENAME} ^(.*)product(.*\.css)$rewriteCond %1main%2 -fRewriteRule ^(.*)product(.*\.css)$ autocompile/combine_css.php?f=$1main$2 [L]#如果xxx.(js||css)有對應的xxx.dev.(js||css) 則將重新導向到.dev.js或.dev.css#這個是給已經合并和已經壓縮好的js或css 來用的#比如lib/jquery.js,你在本地可以下載對應的debug版,修改檔案名稱為jquery.dev.js ,這樣本地也可以調試jquery了rewriteCond %{REQUEST_FILENAME} ^(.*)\.(js|css)$rewriteCond %1.dev.%2 -fRewriteRule ^(.*)\.(js|css)$ $1.dev.$2 [L]


下面就是關鍵combine.php了

include 'notmodified.php';define('BASE', dirname(__FILE__));$file_main = BASE.'/../'.$_GET['f'];$file_config = BASE.'/../js/config.js'; //require.js 的公用設定檔$comment_reg_exp = '/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/m';  //匹配注釋的正則$js_require_reg_exp = '/[^.]\s*require\s*\(\s*[\"\']([^\'\"\s]+)[\"\']\s*\)/';  //提取依賴檔案的正則$exclude_reg_exp = '/\s*exclude\s*:\s*(\[.*?\])/s';  //提取依賴檔案但不需要合并壓縮的正則$alias_reg_exp = '/\s*paths\s*:\s*(\{.*?\})/s';      //提取有別名檔案的正則$data_dep_file = array($file_main, $file_config);  //所依賴檔案的數組,包括自身和config檔案$data_ex_dep_file = array();                       //不需壓縮的依賴檔案數組$data_alias_file = '';                             //有別名的的檔案字串if(file_exists($file_config)){    $text_config = file_get_contents($file_config);    $text_config = preg_replace($comment_reg_exp, '', $text_config);  //去掉注釋    preg_match_all($exclude_reg_exp, $text_config, $matches);    if(isset($matches[1][0])){  //取出不需壓縮的依賴檔案配置        $data_ex_dep_file = json_decode(str_replace("\n",'',str_replace(" ", "", str_replace("'", '"', $matches[1][0]))), true);    }    preg_match_all($alias_reg_exp, $text_config, $matches);    if(isset($matches[1][0])){  //取出有別名的真本文件配置        $data_alias_file = str_replace("\n",'',str_replace(" ", "", str_replace("'", '"', $matches[1][0])));    }   }function get_true_path($alias){  //取出有別名的的真本文件名    global $data_alias_file;    $alias_escape = str_replace('.','\.', str_replace('/','\/',$alias));    $regExp ='/'.$alias_escape.':"(.*?)"/';    preg_match_all($regExp, $data_alias_file, $matches);    if(isset($matches[1][0])){        return $matches[1][0];    }    return $alias;}function get_dep_files($file_name){    global $js_require_reg_exp, $data_dep_file, $data_ex_dep_file, $comment_reg_exp;    if(file_exists($file_name)){        $text_main = file_get_contents($file_name);        $text_main = preg_replace($comment_reg_exp, '', $text_main);        preg_match_all($js_require_reg_exp, $text_main, $matches);        if(isset($matches[1]) && is_array($matches[1]) && count($matches[1]) > 0){  //取出依賴檔案            foreach($matches[1] as $v){                $v = trim($v);                $v_true = get_true_path($v);                $v_path = BASE.'/../js/'.$v_true.(strrchr($v, '.') == '.js' ? '' : '.js');  //所依賴的檔案的完整路徑                if(!in_array($v, $data_ex_dep_file) && !in_array($v_path, $data_dep_file)){                    $data_dep_file[] = $v_path;                    get_dep_files($v_path); //遞迴取出依賴檔案                }            }        }    }}get_dep_files($file_main);$ext = strrchr($file_main, '.');$file_name = basename($file_main, $ext);$file_source = 'app/main/'.$file_name;$file_output = BASE.'/../js/app/product/'.$file_name.'.dev.js';if(file_exists($file_output) && notmodified($data_dep_file)){  //判斷是否需要壓縮,如果沒有修改過,會返回304,去讀緩衝    exit;}exec('node r.js -o mainConfigFile='.BASE.'/../js/config.js baseUrl='.BASE.'/../js/ name='.$file_source.' out='.$file_output.' optimize=none');  //使用node 壓縮,產生dev檔案,optimize=none 是只合并的意思header('Content-Type: application/javascript');echo file_get_contents($file_output);exit;


下面是notmodified.php

function notmodified($files = array()){$s = '';if(is_string($files)){$files = array($files);        }if($files){foreach($files as $f){$s .= filemtime($f); //拼接所依賴檔案修改時間,用來產生eTag的token                }}$etag = sprintf('%08x', crc32($s));header("ETag: \"$etag\"");//輸出eTag if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'], $etag)){header('HTTP/1.1 304 Not Modified');// 如果沒有修改過,則返回304,去讀緩衝return true;}return false;}


下面看下入口檔案main/spc.js

require([javascriptServer+"js/config.js"], function(){// 載入公用的設定檔後,再開始定義模組。javascriptServer是全域變數,放置js的網域名稱,如果 是本域,則不用寫    require(['app/main/spc']);});define(function(require){    "use strict";    var app = require('app/views/app');new app();});


下面看公用設定檔config.js到此已經完成大部分工作了,還剩下最後上生產的壓縮工作

requirejs.config({    baseUrl: typeof(javascriptServer) === 'undefined' ? '' : javascriptServer  + 'js/',    paths: {        jquery: 'lib/jquery-1.11.1.min',        underscore: 'lib/underscore-1.6.min',        backbone: 'lib/backbone-1.1.2.min'    },    useStrict: true,    exclude: ['jquery', 'underscore', 'backbone'],//不需要合并的檔案,使用r.js 進行合并或壓縮時,讀此設定檔:node r.js -o mainConfigFile='.BASE.'/../js/config.js ......    shim: { /*目前backbone和underscore都已支援amd!如果是不支援的版本,則需要下面的配置。               backbone: {                    deps: ['jquery', 'underscore'],                    exports: 'Backbone'               },               underscore: {                    exports: '_'               } */        }});


先看批處理 compile.bat,很簡單

@echo offphp compile.php "../js/app/product"php compile.php "../css/product"pause

再看compile.php,也很簡單

define('BASE', dirname(__FILE__));if($argc == 1){compile(BASE);}else{compile(BASE.'/'.$argv[1]);}function compile($dir){$h = opendir($dir);while(($f = readdir($h)) !== false){if($f == '.' || $f == '..'){continue;              }$f = $dir.'/'.$f;if(is_dir($f)){    compile($f);}else if(substr($f, -7) == '.dev.js'){//壓js             $ext = strrchr($f, '.');     $out = substr($f, 0, -7).'.js';     if(!file_exists($out) || filemtime($f) > filemtime($out)){                         system('node r.js -o mainConfigFile='.BASE.'/../js/config.js baseUrl='.BASE.'/../js/ name=app/product/'.basename($f, $ext).' out='.$out);                     }}else if(substr($f, -8) == '.dev.css'){//壓css$out = substr($f, 0, -8).'.css';if(!file_exists($out) || filemtime($f) > filemtime($out)){system('node r.js -o cssIn='.$f.' out='.$out.' optimizeCss=standard');                        }}}closedir($h);}


最後要說下,其實這個套路有兩個可以容忍小bug,平時需要注意下。

1.如果(往前)修改了本地時間後,再進行壓縮,可能會導致壓縮失敗。
  解決辦法:把時間調正確後,刪除produt/xxx.js ,重新從版本庫裡更新,再進行壓縮。其實注意下壓縮時不要修改本地時間就可以了。

2.如果ctrl+F5 或清空緩衝的話,即使檔案沒有修改,也會重新合并,因為eTag 被清除了!
  解決辦法:這個問題其實不用管,如果是沒修改的被重新壓縮了,不提交就可以了,提交了也沒太大關係!

   目前只能先忍著,如果有好的方法,歡迎指導。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.