XX項目須要開發一套前端組件打包系統,來處理用戶的請求:php
1. 用戶經過平臺申請應用;css
2. 選擇所須要的組件;html
3. 把組件的相關前端文件如js、css、html進行抽取、合併、壓縮、打包;前端
4. 把壓縮包連接地址返回給用戶,用戶下載壓縮包。node
針對上面的需求,咱們選擇一個比較流行的前端代碼打包工具grunt。linux
什麼是grunt?shell
官網給它的解釋是The JavaScript Task Runner。Grunt是基於Node.js的項目構建工具,能夠對項目文件壓縮、編譯、單元測試等任務經過Gruntfile配置用grunt命令自動執行,節省大部分無聊的工做和時間。數據庫
爲何選grunt?npm
由於grunt有豐富的插件,能知足打包所須要的合併、壓縮、打zip包等功能。json
打包系統的運行須要下面的環境提供支持,在開發前須要對其一一安裝。
Node:Javascript運行環境(runtime)。實際上它是對Google V8引擎進行了封裝
npm:全稱Node Package Manager,是一個NodeJS包管理和分發工具
pm2:帶有負載均衡功能的Node應用的進程管理器
Dnode:實現php與node之間的通訊,提供雙向遠程方法調用類庫
Grunt及其插件:grunt-contrib-clean,grunt-contrib-concat,grunt-contrib-copy,grunt-contrib-cssmin,grunt-contrib-less,grunt-contrib-uglify,grunt-contrib-watch,grunt-zip,load-grunt-tasks,gurnt依靠這些插件完成了代碼的合併、壓縮、打zip包等功能
先看一下流程圖
上圖的流程是通過無數次修改後的方案,期間遇到不少問題,主要集中在php和grunt之間的調用和參數傳遞上。須要解決的問題:
1. 怎樣從源代碼文件中根據所選擇的組件來抽取對應的文件;
2. 怎樣用php程序調用grunt命令
3. dnode能夠做爲php調用grunt命令的橋樑,php怎樣同步調用grunt命令
1. Gruntfile文件使用
用戶首先要選擇組件進行下載壓縮包,經過grunt命令給Gruntfile傳遞須要打包的組件列表,Gruntfile包含grunt的所有處理邏輯。
加載grunt插件
加載所須要的插件,寫在package.json文件裏:
"devDependencies": { "grunt": "~0.4.0", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-concat": "^0.1.3", "grunt-contrib-copy": "^0.4.0", "grunt-contrib-cssmin": "^0.6.1", "grunt-contrib-uglify": "^0.9.1", "grunt-contrib-watch": "^0.3.1", "grunt-contrib-less": "^1.0.0", "load-grunt-tasks": "^3.2.0", "time-grunt": "^1.2.1", "grunt-zip": "^0.16.2" }
源文件配置文件:
下面是loading組件的配置文件,html和一些通用js,css (less) 直接寫在GruntFile文件內
{
"app": "loading", "less": ["src/app/loading/loading.less"], "js": ["src/vendor/common/pxloader.js", "src/app/loading/loading.js"] }
Gruntfile參數接收:
var appArr = grunt.option('app').split(','); (app爲組件參數字符串,是組件名稱組合,名稱之間用逗號分隔)例如:’register,login,slider’。
Gruntfile根據獲取的組件列表讀取組件配置文件。
for (var i in appArr) { var confName = 'grunt_conf/' + appArr[i] + '.json'; confArr[i] = grunt.file.readJSON(confName); };
Gruntfile根據參數app獲取組件名稱,而後又根據組件名稱獲取組件配置文件,經過配置文件,能夠獲取源文件的文件列表,而後對這些組件的源文件進行組合、合併、壓縮,最後生成壓縮包
2. php調用grunt
因爲生產環境php.ini的disable_functions把exec、shell_exec、system這樣能夠執行linux命令的函數禁用,可是Node能夠調用,DNode能夠實現php和Node之間的通訊。這樣咱們就能夠實現php程序調用grunt命令。
Node打包Server
var PORT = 7083; var dnode = require('dnode'); var cp = require('child_process'); var server = dnode({ pack: function (params, callback) { var ls = cp.exec("grunt --app=" + params.coms, [], function(error, stdout, stderr){ if(error != null || stdout.indexOf('without errors') < 0){ callback('error'); }else{ callback('success'); } }); } }); server.listen(PORT);
Php同步調用
爲何同步調用而不是異步?由於打包後要對壓縮包上傳,上傳前必須保證壓縮包存在,因此咱們使用了回調函數,而且能夠根據回調函數的返回值判斷打包是否正常,同時也保證下一步上傳的正常進行。
/** * 源文件打包 * @param string $components * @param string $path * @param string $fileName * @throws \H5EException */ private static function sourcePack($components){ $loop = new \React\EventLoop\StreamSelectLoop(); $dnode = new \DNode\DNode($loop); $port = 7083; self::$params = array('coms' => $components); $dnode->connect($port, function($remote, $connection) { $remote->pack(PackService::$params, function ($ret) use ($connection){ if ("success" != $ret) { throw new \H5EException("pack service error", Constants::SYSTEM_CODE); } $connection->end(); }); }); $loop->run(); }
3. 維持Node打包server的持續運行
node啓動打包server進程,一段時間後會莫名其妙的掛掉,pm2做爲node的守護進程很好的解決了這個問題。
4. 壓縮包上傳
壓縮包是根據版原本規劃的,當版本改變後,用戶下載壓縮包就須要從新打包,可是同一版本的源文件不必從新打壓縮包,因此咱們把壓縮包上傳資源服務器上,而後把資源連接保存在數據庫中,下一次只需從數據庫中查詢到該連接便可,而無須重複打包,這樣既提升了用戶下載的速度,又節省了服務器資源。
增長Node日誌,目前缺乏node日誌,若是出現異常,很難定位的問題;
Node打包server接收參數時進行嚴格校驗;
A. 控制訪問頻率,和普通數據接口相比,打包接口耗時較長,消耗服務器資源較多,若是出現接口被惡意頻繁請求,可能會影響服務器性能,同時形成正常的打包失敗,有必要對訪問頻率作限制;
B. 在php層和node層都要進行嚴格校驗參數,能夠有效的防止因參數問題而帶來的意外;
C. Node 代碼打包server運行的端口不能對外,阻止用戶經過外網直接訪問該端口。