乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      【大前端之前后分離01】JS前端渲染VS服務(wù)器端渲染

       liang1234_ 2019-01-30

      前言

      之前看了一篇文章:@Charlie.Zheng Web系統(tǒng)開發(fā)構(gòu)架再思考-前后端的完全分離,文中論述了為何要前后分離,站在前端的角度來看,是很有必要的;但是如何說服團(tuán)隊(duì)使用前端渲染方案卻是一個(gè)現(xiàn)實(shí)問題,因?yàn)槿绻沂且粋€(gè)服務(wù)器端,我便會(huì)覺得不是很有必要,為什么要前后分離,前后分離后遺留了什么問題,如何解決,都得說清楚,這樣才能說服團(tuán)隊(duì)使用前端渲染的方案,而最近我剛好遇到了框架選型的抉擇。

      來到新公司開始新項(xiàng)目了,需要做前端框架選型,因?yàn)橹皟?nèi)部同事采用的fis框架,而這邊又是使用的php,這次也就直接采用fis基于php的解決方案:

      http://oak.baidu.com/fis-plus

      說句實(shí)話,fis這套框架做的不錯(cuò),但是如果使用php方案的話,我就需要蛋疼的在其中寫smarty模板,然后完全按照規(guī)范走,雖然fis規(guī)范比較合理,也可以接受,但是稍微深入解后發(fā)現(xiàn)fis基于php的方案可以概括為(我們的框架用成這樣,不特指fis):

      服務(wù)器端渲染html全部圖給瀏覽器,再加載前端js處理邏輯

      顯然,這個(gè)不是我要的,夢想中的工作方式是做到靜態(tài)html化,靜態(tài)html裝載js,使用json進(jìn)行業(yè)務(wù)數(shù)據(jù)通信,這就是一些朋友所謂的前端渲染了

      JS渲染的鄙利

      前端渲染會(huì)帶來很多好處:

      ① 完全釋放前端,運(yùn)行不需要服務(wù)器;

      ② 服務(wù)器端只提供接口數(shù)據(jù)服務(wù),業(yè)務(wù)邏輯全部在前端,前后分離;

      ③ 一些地方性能有所提升,比如服務(wù)器不需要解析index.html,直接返回即可;

      ④ ......

      事實(shí)上以上的說法和優(yōu)勢皆沒有十足的說服力,根據(jù)上述因素,我們知道了為什么我們要采用js+json的方案,但這不代表應(yīng)該采用。

      比如很多朋友認(rèn)為前后分離可以讓前端代碼更加清晰,這一說法我就十分不認(rèn)同,如果前端代碼功力不夠,絕對可以寫成天書,分離是必要條件,卻不是分離后前端就一定清晰,否則也不會(huì)有那么多人呼吁模塊化、組件化;而且服務(wù)器端完全可以質(zhì)疑這樣做的種種問題,比如:

      ① 前端模板解析對手機(jī)端的負(fù)擔(dān),對手機(jī)電池產(chǎn)生更快的消耗;

      前端渲染頁面內(nèi)容不能被爬蟲識別,SEO等于沒有了;

      ③ 前端渲染現(xiàn)階段沒有完善的ABTesting方案;

      ④ 不能保證一個(gè)URL每次展示的內(nèi)容一致,比如js分頁導(dǎo)致路由不一致;

      ⑤ ......

      以上的問題,一些是難點(diǎn),一些是痛點(diǎn),選取前端渲染方案至少得有SEO解決方案,不然一切都是空談

      所以有如此多的問題,前端憑什么說服團(tuán)隊(duì)使用前端渲染的方案,難道僅僅是我們爽了,我們覺得這樣好就可以了嗎?

      況且現(xiàn)狀是團(tuán)隊(duì)中服務(wù)器端的同事資深的多,前端話語權(quán)不夠,這個(gè)時(shí)候需要用數(shù)據(jù)說話,但未做調(diào)研也拿不出數(shù)據(jù),沒有數(shù)據(jù)你憑什么說服領(lǐng)導(dǎo)采用前端渲染方案?

      為什么要采用前端渲染

      最近兩年我卻找到了可以說服自己采用前端渲染的原因:

      ① 體驗(yàn)更好

      Hybrid內(nèi)嵌只能用靜態(tài)文件

      事實(shí)上我們不能用數(shù)據(jù)說明webapp(前端渲染)的體驗(yàn)就一定比服務(wù)器端渲染好,所以Hybrid內(nèi)嵌就變成了主要的因素,現(xiàn)有的Hybrid有兩種方案:

      ① webview直連線上站點(diǎn),響應(yīng)速度慢,沒有升級負(fù)擔(dān),離線應(yīng)用不易;

      ② 將靜態(tài)html+js+css打包進(jìn)native中,直接走file模式訪問,交互走json,非常簡單就可以實(shí)現(xiàn)離線應(yīng)用(某些頁面的離線應(yīng)用)

      現(xiàn)在一個(gè)產(chǎn)品一般三套應(yīng)用:PC、H5站點(diǎn)、APP,PC站點(diǎn)早就形成,H5站點(diǎn)一般與APP同步開發(fā),Hybrid中的邏輯與H5的邏輯大同小異,所以

      H5站點(diǎn)與Hybrid中的靜態(tài)文件使用一套代碼,這個(gè)是使用前端渲染的主要原因,意思是H5程序結(jié)束,APP就完成80%了。

      因?yàn)榉?wù)器端渲染需要使用動(dòng)態(tài)語言,而webview只能解析html等靜態(tài)文件,所以使用前端渲染就變成了必須,而這一套說辭基本可以說服多數(shù)人,自少我是信了。

      攔路虎-SEO

      上面說了很多前端渲染的問題,什么手機(jī)性能、手機(jī)耗電、ABTesting都不是痛點(diǎn),唯一難受的是H5站點(diǎn)的SEO,以原來公司酒店訂單來說,有20%以上的流量來源于H5站點(diǎn),瀏覽器是一個(gè)流量的重要來源,SEO不可丟棄。

      所以前端渲染必須有解決SEO的方案,并且方法不能太爛,否則框架出來了也沒人愿意用,好在這次做的項(xiàng)目不是webapp,SEO方案相對要簡單一點(diǎn),移動(dòng)端展示的信息少SEO不會(huì)太難,這個(gè)進(jìn)一步降低了我們的實(shí)現(xiàn)難度,經(jīng)過幾輪摸索,我這兩天想了一個(gè)簡單的方案,正在驗(yàn)證可行性。

      JS渲染應(yīng)該如何做

      前端渲染應(yīng)該如何做?阿里的大神們事實(shí)上一直也在思考方案,并且似乎已經(jīng)有成功的產(chǎn)出:前后端分離的思考與實(shí)踐(二)

      可惜,讀過文章后,依舊沒有獲得對自己有用的信息,并且對應(yīng)的代碼也看不到,自己之前的方案:探討webapp的SEO難題(上),連自己都覺得非常戳而沒有繼續(xù)。

      編譯的過程

      而最近在公司內(nèi)部使用fis時(shí)候,一段代碼引起了我的興趣:

      {%block name="body"%}
          {%widget name="webapp:widget/index/route/route.tpl"%}
          {%widget name="webapp:widget/index/searchCity/searchCity.tpl"%}
          {%widget name="webapp:widget/index/selectDate/selectDate.tpl"%}
      {%/block%}

      這段代碼基于smarty模板,運(yùn)行會(huì)經(jīng)過一次release過程,將真正的route模板字符串與服務(wù)器data形成最終的html,這段代碼引起了我的思考,卻說不出來什么問題。

      我偶然又看到了之前的react解決方案,似乎也有一個(gè)編譯的過程:

      復(fù)制代碼
      React.render( 
        // 這是什么不是字符串,不是數(shù)字,又不是變量的參數(shù)……WTF 
        <h1>Hello, world!</h1>, 
        document.getElementById('example') 
      ); 
      //JSX編譯轉(zhuǎn)換為javascript==>
      React.render( 
        React.DOM.h1(null, 'Hello, world!'), 
        document.getElementyById('example') 
      ); 
      復(fù)制代碼

      所以,在程序真實(shí)運(yùn)行前有一個(gè)編譯的過程,一個(gè)是編譯才能運(yùn)行,一個(gè)是運(yùn)行時(shí)候需要編譯,于是我在想前端渲染可以這樣做嗎?

      頁面渲染的條件

      比較簡單的情況下,對于前端來說,頁面html的組成需要數(shù)據(jù)與模板,而服務(wù)器也僅僅需要數(shù)據(jù)與模板,所以簡單來說:

      html = data + template

      前后端的模板有所不同的是:

      前端模板也許不能被服務(wù)器解析,如果模板中存在js函數(shù),服務(wù)器模板將無法執(zhí)行

      但是經(jīng)過我們之前的研究,.net可以運(yùn)行一個(gè)V8的環(huán)境幫助解析模板,java等也有相關(guān)的類庫,所以此問題不予關(guān)注,第二個(gè)問題是:

      前端數(shù)據(jù)為異步加載,服務(wù)器端為同步加載,但是:

      簡單情況下,服務(wù)器端與前端數(shù)據(jù)請求需要的僅僅是URL與參數(shù)

      于是,一個(gè)方案似乎變的可能。

      前端渲染方案

      入口頁

      將如我們的index.html是這樣的:

      debug端:

      復(fù)制代碼
      <!DOCTYPE html>
      <html>
      <head lang="en">
          <meta charset="UTF-8">
          <title></title>
          <script type="text/javascript" src="./libs/zepto.js"></script>
          <script type="text/javascript" src="./libs/underscore.js"></script>
          <script type="text/javascript" src="./libs/require.js"></script>
      </head>
      <body>
      <%widget({
      name: 'type',
      model: 'type',
      controller: 'type'
      }); %>
      </body>
      </html>
      復(fù)制代碼

      其中name對應(yīng)的為模板文件,而model對應(yīng)的是數(shù)據(jù)請求所需文件,controller對應(yīng)控制器,我們這里使用grunt形成兩套前端代碼,分別對應(yīng)服務(wù)器端前端:

      注意:這里服務(wù)器實(shí)現(xiàn)暫時(shí)使用nodeJS,該方案設(shè)想是可以根據(jù)grunt打包支持.net/java/php等語言,但是樓主服務(wù)器戰(zhàn)五渣,所以你懂的

      服務(wù)器端:

      復(fù)制代碼
      <!DOCTYPE html>
      <html>
        <head>
          <title>測試</title>
          <script type="text/javascript" src="./libs/zepto.js"></script>
          <script type="text/javascript" src="./libs/underscore.js"></script>
          <script type="text/javascript" src="./libs/require.js"></script>
        </head>
        <body>
          <%-widget({
            name: 'type',
            model: 'type',
            controller: 'type'
          }); %>
        </body>
      </html>
      復(fù)制代碼

      前端:

      復(fù)制代碼
       1 <!DOCTYPE html>
       2 <html>
       3 <head lang="en">
       4     <meta charset="UTF-8">
       5     <title></title>
       6     <script type="text/javascript" src="./libs/zepto.js"></script>
       7     <script type="text/javascript" src="./libs/underscore.js"></script>
       8     <script type="text/javascript" src="./libs/require.js"></script>
       9     <script type="text/javascript">
      10         require.config({
      11             "paths": {
      12                 "text": "./libs/require.text"
      13             }
      14         });
      15 
      16         var render = function (template, model, controller, wrapperId) {
      17             require([template, model, controller],
      18             function (template, model, controller) {
      19                 //調(diào)用model,生成json數(shù)據(jù)
      20                 model.execute(function (data) {
      21                     data = JSON.parse(data);
      22                     if (data.errorno != 0) return;
      23                     //根據(jù)模板和data生成靜態(tài)html,并形成dom結(jié)構(gòu)準(zhǔn)備插入
      24                     var html = $(_.template(template)(data));
      25                     var wrapper = $('#' + wrapperId);
      26 
      27                     //將dom結(jié)構(gòu)插入,并且將多余的包裹標(biāo)志層刪除
      28                     html.insertBefore(wrapper);
      29                     wrapper.remove();
      30                     //執(zhí)行控制器
      31                     controller.init();
      32                 });
      33             });
      34         };
      35     </script>
      36 </head>
      37 <body>
      38 <div id="type_widget_wrapper">
      39 <script type="text/javascript">
      40     render('text!./template/type.html', './model/type', './controller/type', 'type_widget_wrapper');
      41 </script>
      42 </div>
      43 </body>
      44 </html>
      復(fù)制代碼

      雖然,我這里grunt的程序尚未實(shí)現(xiàn),但是根據(jù)之前的經(jīng)驗(yàn),這是一定能實(shí)現(xiàn)的。

      model的設(shè)計(jì)

      默認(rèn)入口端model為一個(gè)json對象

      debug端&服務(wù)器端:

      {
          "url": "http:///uploads/rs/279/2h5lvbt5/data.json",
          "param": {}
      }

      因?yàn)榉?wù)器端僅僅需要一個(gè)url一個(gè)param,所以服務(wù)器端與debug端保持一致,而前端被grunt加工為:

      復(fù)制代碼
      define(function () {
          return{
              url: './data/data.json',
              param: {},
              execute: function (success) {
                  $.get(this.url, this.param, function (data) {
                      success(data);
                  })
              }
          };
      })
      復(fù)制代碼

      顯然,此數(shù)據(jù)源文件比較簡單,真實(shí)情況不可能如此,我們這里也僅僅做demo說明,后續(xù)逐步加強(qiáng)。

      服務(wù)器端運(yùn)行流程

      服務(wù)器端由于是基于node的,首先需要配置app,這里將所有路由全部放到index.js中:

      View Code

      index的代碼:

      復(fù)制代碼
       1 var express = require('express');
       2 var path = require('path');
       3 var ejs = require('ejs');
       4 var fs= require('fs');
       5 var srequest = require('request-sync');
       6 
       7 var project_path = path.resolve();
       8 var routerCfg = require(project_path + '/routerCfg.json');
       9 
      10 //定義頁面讀取方法,需要同步讀取
      11 var widget = function(opts) {
      12   var model = require(project_path + '/model/' + opts.model + '.json') ;
      13   //var controller =project_path + '/controller/' + opts.controller + '.js';
      14   var tmpt = fs.readFileSync(project_path + '/template/' + opts.name + '.html', 'utf-8');
      15 
      16   //設(shè)置代理,直接使用ip不能讀取數(shù)據(jù),但是設(shè)置代理的化,代理不生效,只能直接讀取線上了......
      17   var res = srequest({ uri: model.url, qs: model.param});
      18 
      19   var html = ejs.render(tmpt, JSON.parse(res.body.toString('utf-8')));
      20 
      21   //插入控制器,這個(gè)路徑可能需要調(diào)整
      22   html += '<script type="text/javascript">require(["controller/' + opts.controller + '"], function(controller){controller.init();});</script>';
      23 
      24   return html;
      25 };
      26 
      27 var initRounter = function(opts, app) {
      28   //根據(jù)路由配置生成路由
      29   for(var k in opts) {
      30     app.get('/' + k, function (req, res) {
      31       res.render(k, { widget: widget});
      32     });
      33   }
      34 };
      35 
      36 module.exports = function(app) {
      37   //加載所有路由配置
      38   initRounter(routerCfg, app);
      39 };
      復(fù)制代碼

      簡單加載流程:

      核心點(diǎn):對于服務(wù)器端來說,widget為一個(gè)javascript方法,會(huì)根據(jù)參數(shù)返回一個(gè)字符串(因?yàn)樾枰椒祷厮阅0遄x取,數(shù)據(jù)訪問皆為同步進(jìn)行)

      ① 訪問/index路徑

      ② 根據(jù)widget參數(shù)獲取model數(shù)據(jù)(json)

      ③ 獲取model url,并且根據(jù)param發(fā)送請求獲取數(shù)據(jù)(這里的情況比較簡單,先不要苛責(zé))

      ④ 根據(jù)參數(shù)獲取模板

      ⑤ 根據(jù)esj模板(類似于undersocre模板),解析生成html

      ⑥ 將控制器代碼一require的方式添加到html,最后返回html

      啟動(dòng)node服務(wù),運(yùn)行之得到了最終結(jié)果:

      運(yùn)行結(jié)果:

      查看源代碼,可以看到有完整的html結(jié)構(gòu):

      View Code

      客戶端流程

      客戶端由于需要異步性,所以生成的結(jié)構(gòu)是這樣的:

      1 <div id="type_widget_wrapper">
      2 <script type="text/javascript">
      3     render('text!./template/type.html', './model/type', './controller/type', 'type_widget_wrapper');
      4 </script>
      5 </div>

      核心代碼為:

      復(fù)制代碼
       1 var render = function (template, model, controller, wrapperId) {
       2     require([template, model, controller],
       3     function (template, model, controller) {
       4         //調(diào)用model,生成json數(shù)據(jù)
       5         model.execute(function (data) {
       6             data = JSON.parse(data);
       7             if (data.errorno != 0) return;
       8             //根據(jù)模板和data生成靜態(tài)html,并形成dom結(jié)構(gòu)準(zhǔn)備插入
       9             var html = $(_.template(template)(data));
      10             var wrapper = $('#' + wrapperId);
      11 
      12             //將dom結(jié)構(gòu)插入,并且將多余的包裹標(biāo)志層刪除
      13             html.insertBefore(wrapper);
      14             wrapper.remove();
      15             //執(zhí)行控制器
      16             controller.init();
      17         });
      18     });
      19 };
      復(fù)制代碼

      ① 頁面加載,開始解析頁面中的render方法

      ② render方法根據(jù)參數(shù)獲取model模塊與template模塊

      ③ 執(zhí)行model.execute異步請求數(shù)據(jù),并與template形成html

      ④ 將html形成jquery對象,插入包裝節(jié)點(diǎn)前,然后刪除節(jié)點(diǎn)

      運(yùn)行結(jié)果:

      查看源代碼,可以看到,這些代碼與seo毫無關(guān)系:

      View Code

      整體目錄

      PS:目錄有一定缺少,因?yàn)槌绦蛏形赐耆瓿?,而最近工作忙起來?.....

      問題&后續(xù)

      因?yàn)檫@個(gè)方案是自己想的,肯定認(rèn)為是有一定可行性的,但是有幾個(gè)問題必須得解決。

      debug煩

      如所示,開始階段我們一般都只開發(fā)debug層,但是要調(diào)試卻每次需要grunt工具release一下才能運(yùn)行client中的程序,顯然不好,需要解決。

      模板嵌套

      模板嵌套問題事實(shí)上是最難的,想象一下,我們在一個(gè)模板中又有一個(gè)widget,在子模板中又有一個(gè)widget,這個(gè)就變成了一個(gè)噩夢,這里的嵌套最怕的是,父模塊與子模塊中有數(shù)據(jù)依賴,或者子模塊為一個(gè)循環(huán),循環(huán)卻依賴父模塊單個(gè)值,這個(gè)非常難解決。

      后續(xù)

      這個(gè)想法最近才出現(xiàn),剛剛實(shí)現(xiàn)必定會(huì)有這樣那樣的問題,而且自己的知識體系也達(dá)不到架構(gòu)水平,如果您發(fā)現(xiàn)文中任何問題,或者有更好的方案,請您留言,后續(xù)這塊的研究暫時(shí)規(guī)劃為:

      ① 完善grunt程序,形成.net方案

      ② 解決debug時(shí)候需要編譯問題

      ③ 解決模板嵌套、模塊數(shù)據(jù)依賴問題

      ④ ......

      github

      https://github.com/yexiaochai/sword

      微博求粉

        本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多