前言
前端的技術發展真的快,好了,廢話不多說,先說說使用browserify及gulp配合建構的例子以及個人認為的缺陷。
後面會将這個項目的建構工具以及demo源代碼發出來的。。。但是但是但是,browserify+gulp還是沒能解決開發和在用戶端部分加載的問題,也沒辦法打包成為多個檔案,然後像seajs那樣子產品加載—-這個可是很緻命的。browserify的出現至少展示了前端工程化的新方向—-預編譯,但是還差一點,而我就是要解決相差的那一點甚至為了解決這個問題而要推倒重來—甚至不用browserify。
不過說起來,這個demo和建構工具雖然是半成品,但也可以給大家一些啟示,起碼我自己在做這個預編譯樣式和代碼的時候逐漸明白了方向。
簡介
建構的系統大約分成幾部分–針對每一個項目的配置檔案,build.js,建構工具以及建構的産物和資源清單。
大約如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISM0kTMyYDN2EDNxATM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
還有,一個用來示範的建構網站,php+composer的,而mvc的隻是個人根據網上的改改而成。
我們還是先來看看示範網站的實際成品吧,看看建構出來的結果産物:
靜态資源的建構規則
1、對于成熟的外部類庫,譬如,jquery,mement這些,可以指定為靜态資源,然後,建構工具會自動計算出這些資源的md5碼,然後就會在頁面上面建構引用url。
來看看jquery的引用過程:
a、在配置檔案build。js這樣設定:
b、工具建構後得到的對應的資源表:
c、好了,在頁面如何引用:
頁面的引用是根據配置檔案規定的資源id;來的,事實上,前端建構以後,所有資源都應該用檔案列出來,而資源路徑都應該要自動建構,所有資源都帶有md5甚至樣式和腳本和圖檔名字都包含md5的一個重要考量是,建構出來的檔案可以作為本地緩存,以後改改路徑就可以了,徹底利用緩存。
而腳本路徑引用邏輯是這樣的:
<?php
/**
* Created by PhpStorm.
* User: hasee
* Date: 2016/10/5
* Time: 13:49
*/
class UrlHelper{
private static $res_json;
private static $res_setting_json;
public function __construct()
{
if(!isset(self::$res_json)){
$_content=file_get_contents(BASE_PATH."/data/resource.json");
self::$res_json = json_decode($_content, true);
$_content=file_get_contents(BASE_PATH."/data/res.setting.json");
self::$res_setting_json = json_decode($_content, true);
}
}
public function CssUrl($resID){
$res_mode="dev";
if (self::$res_setting_json["mode"]=="dev"){
$res_mode="dev";
}
else{
$res_mode="dist";
}
if(self::$res_json["styles"]&&self::$res_json["styles"][$resID]){
$res_rule=self::$res_json["styles"][$resID];
return CONTEXTPATH.$res_rule[$res_mode];
}
else{
return '<!--css resource named '.$resID.' is not found or mode '.$res_mode.' is not found-->';
}
}
public function JsUrl($resID){
$res_mode="dev";
if (self::$res_setting_json["mode"]=="dev"){
$res_mode="dev";
}
else{
$res_mode="dist";
}
if(self::$res_json["scripts"]&&self::$res_json["scripts"][$resID]){
$res_rule=self::$res_json["scripts"][$resID];
return CONTEXTPATH.$res_rule[$res_mode];
}
else{
return '<!--js resource named '.$resID.' is not found or mode '.$res_mode.' is not found-->';
}
}
public function StaticCssUrl($resID){
$res_mode="dev";
if (self::$res_setting_json["mode"]=="dev"){
$res_mode="dev";
}
else{
$res_mode="dist";
}
if(self::$res_json["static"]["styles"]&&self::$res_json["static"]["styles"][$resID]){
$res_rule=self::$res_json["static"]["styles"][$resID];
return CONTEXTPATH.$res_rule[$res_mode]."?v=".$res_rule["md5"];
}
else{
return '<!--css resource named '.$resID.' is not found or mode '.$res_mode.' is not found-->';
}
}
public function StaticJsUrl($resID){
$res_mode="dev";
if (self::$res_setting_json["mode"]=="dev"){
$res_mode="dev";
}
else{
$res_mode="dist";
}
if(self::$res_json["static"]["scripts"]&&self::$res_json["static"]["scripts"][$resID]){
$res_rule=self::$res_json["static"]["scripts"][$resID];
return CONTEXTPATH.$res_rule[$res_mode]."?v=".$res_rule["md5"];
}
else{
return '<!--js resource named '.$resID.' is not found or mode '.$res_mode.' is not found-->';
}
}
public function CssForModule($module,$resID){
$res_mode="dev";
if (self::$res_setting_json["mode"]=="dev"){
$res_mode="dev";
}
else{
$res_mode="dist";
}
if(self::$res_json["modules"][$module]&&self::$res_json["modules"][$module]["styles"]&&self::$res_json["modules"][$module]["styles"][$resID]){
$res_rule=self::$res_json["modules"][$module]["styles"][$resID];
return CONTEXTPATH.$res_rule[$res_mode];
}
else{
return '<!--css resource named '.$resID.' is not found or mode '.$res_mode.' is not found-->';
}
}
public function JsForModule($module,$resID){
$res_mode="dev";
if (self::$res_setting_json["mode"]=="dev"){
$res_mode="dev";
}
else{
$res_mode="dist";
}
if(self::$res_json["modules"][$module]&&self::$res_json["modules"][$module]["scripts"]&&self::$res_json["modules"][$module]["scripts"][$resID]){
$res_rule=self::$res_json["modules"][$module]["scripts"][$resID];
return CONTEXTPATH.$res_rule[$res_mode];
}
else{
return '<!--js resource named '.$resID.' is not found or mode '.$res_mode.' is not found-->';
}
}
public function ActionUrlFor($model,$actionName){
$_php_name="/admin.php";
if($model=="admin"){}
else{
$_php_name="/index.php";
}
return CONTEXTPATH.$_php_name."/".$actionName;
}
}
看不懂不要緊,因為這裡隻是着重說一下相關流程,等下會有demo放出去慢慢嘗試的。
d、頁面上面的渲染結果:
樣式和圖檔的建構規則
樣式和圖檔的建構,樣式支援scss,支援import,同時可以設定多個樣式合并在一起,還有就是,建構樣式的時候會将圖檔自動放到産出目錄,給圖檔加上md5特征碼,順便将樣式裡面對應圖檔的路徑改成産出圖檔路徑。
現在我們來看看一個例子:
a、我們現在有兩個樣式,叫global.scss,reset.scss需要合并在一起,還有一個樣式叫login.scss的,需要建構一下,用于登入頁面。
登入的樣式:
我們需要這樣設定:
建構以後變成了:
資源檔案對應部分如下:
好吧。。我将全部内容都貼出來算了,等下再解釋:
{
"styles": {
"all": {
"dev": "/static/dist/css/all.2b7f3e80f486e8e74e1b7c06fa0f283f.css",
"dist": "/static/dist/css/all.2b7f3e80f486e8e74e1b7c06fa0f283f.min.css",
"md5": "2b7f3e80f486e8e74e1b7c06fa0f283f"
}
},
"scripts": {
"index": {
"dev": "/static/dist/js/index.4bec43ad342ba77e520e070fdab275b5.js",
"dist": "/static/dist/js/index.4bec43ad342ba77e520e070fdab275b5.min.js",
"md5": "4bec43ad342ba77e520e070fdab275b5"
},
"base": {
"dev": "/static/dist/js/base.f4adca34785cd216d7a53aba2f69b437.js",
"dist": "/static/dist/js/base.f4adca34785cd216d7a53aba2f69b437.min.js",
"md5": "f4adca34785cd216d7a53aba2f69b437"
}
},
"static": {
"styles": {},
"scripts": {
"jquery": {
"dev": "/static/lib/jquery-1.8.0.js",
"dist": "/static/lib/jquery-1.8.0.min.js",
"md5": "04903dc2c64729e82e640d527223c9af"
},
"seajs": {
"dev": "/static/lib/sea-debug.js",
"dist": "/static/lib/sea.js",
"md5": "1c05fca93443a604bb4455873366ad21"
},
"ResourceHelper": {
"dev": "/static/helpers/ResourceHelper.js",
"dist": "/static/helpers/ResourceHelper.js",
"md5": "36ee7f820fdc2b043bedc6dbb2ce3f53"
},
"moment": {
"dev": "/static/lib/moment.js",
"dist": "/static/lib/moment.min.js",
"md5": "0dd576e025858b47248c33dff8190711"
},
"react": {
"dev": "/static/lib/react.js",
"dist": "/static/lib/react.min.js",
"md5": "b941048c2c0025f56c5ecb0e4ecdc2c5"
},
"react-dom": {
"dev": "/static/lib/react-dom.js",
"dist": "/static/lib/react-dom.min.js",
"md5": "ee7a0372099ba328275eedc45c8d34b6"
},
"ejs": {
"dev": "/static/lib/ejs_production.js",
"dist": "/static/lib/ejs_production.js",
"md5": "bf9e301590ff13ac104b924e54632f7c"
},
"laytpl": {
"dev": "/static/lib/laytpl.js",
"dist": "/static/lib/laytpl.js",
"md5": "ae95df82da180a6cca9911eba78eb635"
}
}
},
"modules": {
"admin": {
"styles": {
"login": {
"dev": "/static/dist/css/login.a1554d99bc9bae2ec8e7b03bc6127388.css",
"dist": "/static/dist/css/login.a1554d99bc9bae2ec8e7b03bc6127388.min.css",
"md5": "a1554d99bc9bae2ec8e7b03bc6127388" }
},
"scripts": {
"login": {
"dev": "/static/dist/js/login.cdbc1ea9b42d494cf1a9ebd5fad975b1.js",
"dist": "/static/dist/js/login.cdbc1ea9b42d494cf1a9ebd5fad975b1.min.js",
"md5": "cdbc1ea9b42d494cf1a9ebd5fad975b1" }
}
}
}
}
可以看到所有擁有資源id的資源都包括在内。
然後引用的頁面内容如下:
<link href="<?php echo CssUrl('all')?>" rel="stylesheet" type="text/css" />
<link href="<?php echo CssForModule("admin","login")?>" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="<?php echo StaticJsUrl('jquery'); ?>"></script>
<script type="text/javascript" src="<?php echo StaticJsUrl('moment'); ?>"></script>
<script type="text/javascript" src="<?php echo StaticJsUrl('react'); ?>"></script>
<script type="text/javascript" src="<?php echo StaticJsUrl('react-dom'); ?>"></script>
<script type="text/javascript" src="<?php echo StaticJsUrl('ResourceHelper'); ?>"></script>
<script type="text/javascript" src="<?php echo StaticJsUrl('laytpl'); ?>"></script>
<script type="text/javascript" src="<?php echo StaticJsUrl('seajs'); ?>"></script>
實際渲染效果如下:
<link href="/eFace/static/dist/css/all.2b7f3e80f486e8e74e1b7c06fa0f283f.css" rel="stylesheet" type="text/css" />
<link href="/eFace/static/dist/css/login.a1554d99bc9bae2ec8e7b03bc6127388.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/eFace/static/lib/jquery-1.8.0.js?v=04903dc2c64729e82e640d527223c9af"></script>
<script type="text/javascript" src="/eFace/static/lib/moment.js?v=0dd576e025858b47248c33dff8190711"></script>
<script type="text/javascript" src="/eFace/static/lib/react.js?v=b941048c2c0025f56c5ecb0e4ecdc2c5"></script>
<script type="text/javascript" src="/eFace/static/lib/react-dom.js?v=ee7a0372099ba328275eedc45c8d34b6"></script>
<script type="text/javascript" src="/eFace/static/helpers/ResourceHelper.js?v=36ee7f820fdc2b043bedc6dbb2ce3f53"></script>
<script type="text/javascript" src="/eFace/static/lib/laytpl.js?v=ae95df82da180a6cca9911eba78eb635"></script>
<script type="text/javascript" src="/eFace/static/lib/sea-debug.js?v=1c05fca93443a604bb4455873366ad21"></script>
腳本的建構規則
好了,看了前面兩個,實際上腳本的建構也差不多。
但缺點在于,browserify生成的腳本實際上隻是放到同一個檔案夾裡面,然後自己定義了一個require,将子產品都放在内部的資料裡面,沒有辦法動态加載任何一個這樣的腳本然後再加載裡面的任意一個子產品。
請看看源代碼:
(function outer (modules, cache, entry) {
// Save the require from previous bundle to this closure if any
var previousRequire = typeof require == "function" && require;
function newRequire(name, jumped){
if(!cache[name]) {
if(!modules[name]) { // if we cannot find the module within our internal map or // cache jump to the current global require ie. the last bundle // that was added to the page. var currentRequire = typeof require == "function" && require; if (!jumped && currentRequire) { return currentRequire(name, true); } // If there are other bundles on this page the require from the // previous one is saved to 'previousRequire'. Repeat this as // many times as there are bundles until the module is found or // we exhaust the require chain. if (previousRequire) { return previousRequire(name, true); } var err = new Error('Cannot find module \'' + name + '\''); err.code = 'MODULE_NOT_FOUND'; throw err; }
var m = cache[name] = {exports:{}};
modules[name][0].call( m.exports, function(x) { var id = modules[name][1][x]; return newRequire(id ? id : x); }, m, m.exports, outer, modules, cache, entry);
}
return cache[name].exports;
}
for(var i=0;i<entry.length;i++)
{
newRequire(entry[i]);
}
// Override the current require with this new one
return newRequire;
})({1:[function(require,module,exports){ /** * Created by hasee on/10/5. */ var WebUI = { showConfirm: function (opts) { console.log("confirm pls."); } }; module.exports = WebUI; },{}],2:[function(require,module,exports){ var Child = React.createClass({ displayName: "Child", render: function () { return React.createElement("div", null, " The Child "); } }); module.exports = Child; },{}],3:[function(require,module,exports){ /** * Created by hasee on/10/5. */ //--前端日志記錄,以便性能優化。 var Logger = { log: function (message) { console.log("run Logger.log"); } }; module.exports = Logger; },{}],4:[function(require,module,exports){ var Child = require('./Child.jsx'); var Parent = React.createClass({ displayName: "Parent", render: function () { return React.createElement("div", null, React.createElement("div", null, " Hello World "), React.createElement(Child, null)); } }); module.exports = Parent; },{"./Child.jsx":2}],5:[function(require,module,exports){ /** * Created by hasee on/10/5. */ var Logger = require('./assets/logger'); var WebUI = require('./assets/WebUI.js'); var Parent = require('./assets/Parent.jsx'); var NumberSelector = ""; var app = { init: function () { Logger.log("hello"); setTimeout(function () { WebUI.showConfirm({}); },); ReactDOM.render(React.createElement(Parent, null), document.getElementById('app')); //--模闆渲染。 this.renderList(); var t2 = NumberSelector; }, renderList: function () { var tplStr = ResourceHelper.getTemplateFromBaseDir("<h3>{{ d.title }}</h3> <p class=\"intro\">{{ d.intro }}</p> <ul> {{# for(var i = 0, len = d.list.length; i < len; i++){ }} <li> <span>{{ d.list[i].name }}</span> <span>所在城市:{{ d.list[i].city }}</span> </li> {{# } }} </ul>"); //第三步:渲染模版 var data = { title: '前端攻城師', list: [{ name: '賢心', city: '杭州' }, { name: '謝亮', city: '北京' }, { name: '淺淺', city: '杭州' }, { name: 'Dem', city: '北京' }] }; $("#log").append(laytpl(tplStr).render(data)); } }; $(function () { app.init(); }); },{"./assets/Parent.jsx":4,"./assets/WebUI.js":1,"./assets/logger":3}]},{},[5]);
話說
./assets/Parent.jsx
這種形式的require真的醜到爆,當然,browserify可以用将npm install了的子產品也打包進去檔案裡面。。
但是我覺得這個真的是扯談。。。服務端的代碼跟用戶端浏覽器本來就不一樣的,硬要兩者合并到時候死都不知道怎麼死。
還有一點就是,不支援子產品分割。。。有些子產品是基本不變的,譬如,我們自己寫的ui插件,有些是經常變化的,譬如,業務邏輯。我們需要一個機制可以有子產品分割,浏覽器加載。seajs當時也做得挺好的。
難道就沒有一種像java那樣,在檔案裡面寫個package xxx,然後在其他檔案上面導入命名空間,譬如,import(‘xxxx’)這樣就可以用的機制嗎?當然有,下一篇文章就是完成這個的。但是不知道什麼時候會寫。
資源下載下傳及相關
需要注意的是,建構工具需要npm install一下,然後設定檔案裡面:
configFile需要換成你自己的項目目錄。
下載下傳位址:
這裡寫連結内容
b、php項目用的是composer,請自行搭建環境然後安裝類庫。
用來這麼多類庫的,還要搭建apache伺服器,你懂得。
下載下傳位址:
這裡寫連結内容