整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          如何搭建一個高可用的服務(wù)端渲染工程

          如何搭建一個高可用的服務(wù)端渲染工程

          能大家在看到這個標(biāo)題的時候,會覺得,只不過又是一篇爛大街的 SSR 從零入門的教程而已。別急,往下看,相信你或多或少會有一些不一樣的收獲呢。

          在落地一種技術(shù)的時候,我們首先要想一想:

          1. 是否一定需要引入這種技術(shù)呢?他能解決什么問題,或者能帶來什么收益?
          2. 為什么要采用這種技術(shù)選型而不是其他的?
          3. 引入了這種技術(shù)后,會帶來什么問題嗎(比如額外的開發(fā)成本等)?

          上面三個問題思考清楚之后,才能真正地去落地。上面三個問題思考清楚之后,才能真正地去落地。而有贊教育接入服務(wù)端渲染,正是為了優(yōu)化 H5 頁面的首屏內(nèi)容到達(dá)時間,帶來更好的用戶體驗(順便利于 SEO)。

          說了這么多,以下開始正文。

          一、后端模版引擎時代

          在較早時期,前后端的配合模式為:后端負(fù)責(zé)服務(wù)層、業(yè)務(wù)邏輯層和模版渲染層(表現(xiàn)層);前端只是實現(xiàn)頁面的交互邏輯以及發(fā)送 AJAX。比較典型的例子就是 JSP 或 FreeMarker 模板引擎負(fù)責(zé)渲染出 html 字符串模版,字符串模版里的 js 靜態(tài)資源才是真正前端負(fù)責(zé)的東西。

          而這種形式,就是天然的服務(wù)端渲染模式:用戶請求頁面 -> 請求發(fā)送到應(yīng)用服務(wù)器 -> 后端根據(jù)用戶和請求信息獲取底層服務(wù) -> 根據(jù)服務(wù)返回的數(shù)據(jù)進(jìn)行組裝,同時 JSP 或 FreeMarker 模版引擎根據(jù)組裝的數(shù)據(jù)渲染為 html 字符串 -> 應(yīng)用服務(wù)器講 html 字符串返回給瀏覽器 -> 瀏覽器解析 html 字符串渲染 UI 及加載靜態(tài)資源 -> js 靜態(tài)資源加載完畢界面可交互。

          那么既然后端模版引擎時代帶來的效果就是我們想要的,那為啥還有以后讓前端發(fā)展服務(wù)端渲染呢?因為很明顯,這種模式從開發(fā)角度來講還有挺多的問題,比如:

          1. 后端需要寫表現(xiàn)層的邏輯,但其實后端更應(yīng)該注重服務(wù)層(和部分業(yè)務(wù)邏輯層)。當(dāng)然,其實也可以讓前端寫 JSP 或 FreeMarker,但從體驗上來說,肯定不如寫 JS 來的爽;
          2. 本地開發(fā)的時候,需要啟動后端環(huán)境,比如 Tomcat,影響開發(fā)效率,對前端也不友好;
          3. 所賦予前端的能力太少,使得前端需要的一些功能只能由后端提供,比如路由控制;
          4. 前后端耦合。

          二、SPA 時代

          后來,誕生了 SPA(Single Page Application),解決了上面說的部分問題:

          1. 后端不需要關(guān)心表現(xiàn)層的邏輯,只需要注重服務(wù)層和業(yè)務(wù)邏輯層就可以了,暴露出相應(yīng)的接口供前端調(diào)用。這種模式也同時實現(xiàn)了前后端解耦。
          2. 本地開發(fā)的時候,前端只需要啟動一個本地服務(wù),如:dev-server 就可以開始開發(fā)了。
          3. 賦予了前端更多的能力,比如前端的路由控制和鑒權(quán),比如通過 SPA + 路由懶加載的模式可以帶來更好的用戶體驗。

          但同時,也帶來了一些問題:

          1. 頁面的 DOM 完全由 js 來渲染,使得大部分搜索引擎無法爬取渲染后真實的 DOM,不利于 SEO。
          2. 頁面的首屏內(nèi)容到達(dá)時間強(qiáng)依賴于 js 靜態(tài)資源的加載(因為 DOM 的渲染由 js 來執(zhí)行),使得在網(wǎng)絡(luò)越差的情況下,白屏?xí)r間大幅上升。

          三、服務(wù)端渲染

          正因為 SPA 帶來的一些問題(尤其是首屏白屏的問題),接入服務(wù)端渲染顯得尤為必要。// 終于講到服務(wù)端渲染這個重點了。

          而正是 Node 的發(fā)展和基于 Virtual DOM 的前端框架的出現(xiàn),使得用 js 實現(xiàn)服務(wù)端渲染成為可能。因此在 SPA 的優(yōu)勢基礎(chǔ)上,我們順便解決了因為 SPA 引入的問題:

          1. 服務(wù)端渲染的首屏直出,使得輸出到瀏覽器的就是完備的 html 字符串模板,瀏覽器可以直接解析該字符串模版,因此首屏的內(nèi)容不再依賴 js 的渲染。
          2. 正是因為服務(wù)端渲染輸出到瀏覽器的是完備的 html 字符串,使得搜索引擎能抓取到真實的內(nèi)容,利于 SEO。
          3. 同時,通過基于 Node 和前端 MVVM 框架結(jié)合的服務(wù)端渲染,有著比后端模版引擎的服務(wù)端渲染更明顯的優(yōu)勢:可以優(yōu)雅降級為客戶端渲染(這個后續(xù)會講,先占個坑)。

          3.1 實現(xiàn)

          既然服務(wù)端渲染能帶來這么多好處,那具體怎么實現(xiàn)呢?從官網(wǎng)給出的原理圖,我們可以清晰地看出:

          • Source 為我們的源代碼區(qū),即工程代碼;
          • Universal Appliation Code 和我們平時的客戶端渲染的代碼組織形式完全一致,只是需要注意這些代碼在 Node 端執(zhí)行過程觸發(fā)的生命周期鉤子不要涉及 DOM 和 BOM 對象即可;
          • 比客戶端渲染多出來的 app.js、Server entry 、Client entry 的主要作用為:app.js 分別給 Server entry 、Client entry 暴露出 createApp() 方法,使得每個請求進(jìn)來會生成新的 app 實例。而 Server entry 和 Client entry 分別會被 webpack 打包成 vue-ssr-server-bundle.json 和 vue-ssr-client-manifest.json(這兩個 json 文件才是有用的,app.js、Server entry 、Client entry 可以抽離,開發(fā)者不感知);
          • Node 端會根據(jù) webpack 打包好的 vue-ssr-server-bundle.json,通過調(diào)用 createBundleRenderer 生成 renderer 實例,再通過調(diào)用 renderer.renderToString 生成完備的 html 字符串;
          • Node 端將 render 好的 html 字符串返回給 Browser,同時 Node 端根據(jù) vue-ssr-client-manifest.json 生成的 js 會和 html 字符串 hydrate,完成客戶端激活 html,使得頁面可交互。

          3.2 優(yōu)化

          按照 Vue SSR 官方文檔建立起一個服務(wù)端渲染的工程后,是否就可以直接上線了呢?別急,我們先看看是否有什么可以優(yōu)化的地方。

          3.2.1 路由和代碼分割

          一個大的 SPA,主文件 js 往往很大,通過代碼分割可以將主文件 js 拆分為一個個單獨的路由組件 js 文件,可以很大程度上減小首屏的資源加載體積,其他路由組件可以預(yù)加載。

           復(fù)制代碼

          // router.js
          constIndex=()=>import(/* webpackChunkName: "index" */'./pages/Index.vue');
          constDetail=()=>import(/* webpackChunkName: "detail" */'./pages/Detail.vue');
          constroutes=[
          {
          path:'/',
          component: Index
          },
          {
          path:'/detail',
          component: Detail
          }
          ];
          constrouter=newRouter({
          mode:'history',
          routes
          });
          

          3.2.2 部分模塊(不需要 SSR 的模塊)客戶端渲染

          因為服務(wù)端渲染是 CPU 密集型操作,非首屏的模塊或者不重要的模塊(比如底部的推薦列表)完全可以采用客戶端渲染,只有首屏的核心模塊采用服務(wù)端渲染。這樣做的好處是明顯的:1. 較大地節(jié)省 CPU 資源;2. 減小了服務(wù)端渲染直出的 html 字符串長度,能夠更快地響應(yīng)給瀏覽器,減小白屏?xí)r間。

           復(fù)制代碼

          // Index.vue
          asyncData({ store }) {
          returnthis.methods.dispatch(store);// 核心模塊數(shù)據(jù)預(yù)取,服務(wù)端渲染
          }
          mounted() {
          this.initOtherModules();// 非核心模塊,客戶端渲染,在 mounted 生命周期鉤子里觸發(fā)
          }
          

          3.2 3 頁面緩存 / 組件緩存

          頁面緩存一般適用于狀態(tài)無關(guān)的靜態(tài)頁面,命中緩存直接返回頁面;組件緩存一般適用于純靜態(tài)組件,也可以一定程度上提升性能。

           復(fù)制代碼

          // page-level caching
          constmicroCache=LRU({
          max:100,
          maxAge:1000// 重要提示:條目在 1 秒后過期。
          })
          server.get('*', (req, res)=> {
          consthit=microCache.get(req.url)
          if(hit) {// 命中緩存,直接返回頁面
          returnres.end(hit)
          }
          // 服務(wù)端渲染邏輯
          ...
          })
          

           復(fù)制代碼

          // component-level caching
          // server.js
          constLRU=require('lru-cache')
          constrenderer=createRenderer({
          cache: LRU({
          max:10000,
          maxAge: ...
          })
          });
          // component.js
          exportdefault{
          name:'item',// 必填選項
          props: ['item'],
          serverCacheKey:props=>props.item.id,
          render (h) {
          returnh('div',this.item.id)
          }
          };
          

          3.2.4 頁面靜態(tài)化

          如果工程中大部分頁面都是狀態(tài)相關(guān)的,所以技術(shù)選型采用了服務(wù)端渲染,但有部分頁面是狀態(tài)無關(guān)的,這個時候用服務(wù)端渲染就有點浪費(fèi)資源了。像這些狀態(tài)無關(guān)的頁面,完全可以通過 Nginx Proxy Cache 緩存到 Nginx 服務(wù)器,可以避免這些流量打到應(yīng)用服務(wù)器集群,同時也能減少響應(yīng)的時間。

          3.3 降級

          進(jìn)行優(yōu)化之后,是否就可以上線了呢?這時我們想一想,萬一服務(wù)端渲染出錯了怎么辦?萬一服務(wù)器壓力飆升了怎么辦(因為服務(wù)端渲染是 CPU 密集型操作,很耗 CPU 資源)?為了保證系統(tǒng)的高可用,我們需要設(shè)計一些降級方案來避免這些。具體可以采用的降級方案有:

          • 單個流量降級 – 偶發(fā)的服務(wù)端渲染失敗降級為客戶端渲染
          • Disconf / Apollo 配置降級 – 分布式配置平臺修改配置主動降級,比如可預(yù)見性的大流量情況下(雙十一),可提前通過配置平臺將整個應(yīng)用集群都降級為客戶端渲染
          • CPU 閾值降級 – 物理機(jī) / Docker 實例 CPU 資源占用達(dá)到閾值觸發(fā)降級,避免負(fù)載均衡服務(wù)器在某些情況下給某臺應(yīng)用服務(wù)器導(dǎo)入過多流量,使得單臺應(yīng)用服務(wù)器的 CPU 負(fù)載過高
          • 旁路系統(tǒng)降級 – 旁路系統(tǒng)跑定時任務(wù)監(jiān)控應(yīng)用集群狀態(tài),集群資源占用達(dá)到設(shè)定閾值將整個集群降級(或觸發(fā)集群的自動擴(kuò)容)
          • 渲染服務(wù)集群降級 – 若渲染服務(wù)和接口服務(wù)是獨立的服務(wù),當(dāng)渲染服務(wù)集群宕機(jī),html 的獲取邏輯回溯到 Nginx 獲取,此時觸發(fā)客戶端渲染,通過 ajax 調(diào)用接口服務(wù)獲取數(shù)據(jù)

          3.4 上線前準(zhǔn)備

          3.4.1 壓測

          壓測可以分為多個階段:本地開發(fā)階段、QA 性能測試階段、線上階段。

          • 本地開發(fā)階段:當(dāng)本地的服務(wù)端渲染開發(fā)完成之后,首先需要用 loadtest 之類的壓測工具壓下性能如何,同時可以根據(jù)壓測出來的數(shù)據(jù)做一些優(yōu)化,如果有內(nèi)存泄漏之類的 bug 也可以在這個階段就能被發(fā)現(xiàn)。
          • QA 性能測試階段:當(dāng)通過本地開發(fā)階段的壓測之后,我們的代碼已經(jīng)是經(jīng)過性能優(yōu)化且沒有內(nèi)存泄漏之類嚴(yán)重 bug 的。部署到 QA 性能測試環(huán)境之后,通過壓真實 QA 環(huán)境,和原來的客戶端渲染做對比,看 QPS 會下降多少(因為服務(wù)端渲染耗更多的 CPU 資源,所以 QPS 對比客戶端渲染肯定會有下降)。
          • 線上階段:QA 性能測試階段壓測過后,若性能指標(biāo)達(dá)到原來的預(yù)期,部署到線上環(huán)境,同時可以開啟一定量的壓測,確保服務(wù)的可用性。

          3.4.2 日志

          作為生產(chǎn)環(huán)境的應(yīng)用,肯定不能“裸奔”,必須接入日志平臺,將一些報錯信息收集起來,以便之后問題的排查。

          3.4.3 灰度

          如果上線服務(wù)端渲染的工程是提供核心服務(wù)的應(yīng)用,應(yīng)該采用灰度發(fā)布的方式,避免全量上線。一般灰度方案可以采用:百分比灰度、白名單灰度、自定義標(biāo)簽灰度。具體采用哪種灰度方式看場景自由選擇,每隔一段時間觀察灰度集群沒有問題,所以漸漸增大灰度比例 / 覆蓋范圍,直到全量發(fā)布。

          3.5 落地

          在有贊電商的服務(wù)端渲染的落地場景中,我們抽離了單獨的依賴包,提供各個能力。

          3.6 效果

          從最終的上線效果來看,相同功能的頁面,服務(wù)端渲染的首屏內(nèi)容時間比客戶端渲染提升了 300%+。

          3.7 Q & A

          Q1:為什么服務(wù)端渲染就比客戶端渲染快呢?

          A:首先我們明確一點,服務(wù)端渲染比客戶端渲染快的是首屏的內(nèi)容到達(dá)時間(而非首屏可交互時間)。至于為什么會更快,我們可以從兩者的 DOM 渲染過程來對比:

          客戶端渲染:瀏覽器發(fā)送請求 -> CDN / 應(yīng)用服務(wù)器返回空 html 文件 -> 瀏覽器接收到空 html 文件,加載的 css 和 js 資源 -> 瀏覽器發(fā)送 css 和 js 資源請求 -> CDN / 應(yīng)用服務(wù)器返回 css 和 js 文件 -> 瀏覽器解析 css 和 js -> js 中發(fā)送 ajax 請求到 Node 應(yīng)用服務(wù)器 -> Node 服務(wù)器調(diào)用底層服務(wù)后返回結(jié)果 -> 前端拿到結(jié)果 setData 觸發(fā) vue 組件渲染 -> 組件渲染完成

          服務(wù)端渲染:瀏覽器發(fā)送請求 -> Node 應(yīng)用服務(wù)器匹配路由 -> 數(shù)據(jù)預(yù)取:Node 服務(wù)器調(diào)用底層服務(wù)拿到 asyncData 存入 store -> Node 端根據(jù) store 生成 html 字符串返回給瀏覽器 -> 瀏覽器接收到 html 字符串將其激活:

          我們可以很明顯地看出,客戶端渲染的組件渲染強(qiáng)依賴 js 靜態(tài)資源的加載以及 ajax 接口的返回時間,而通常一個 page.js 可能會達(dá)到幾十 KB 甚至更多,很大程度上制約了 DOM 生成的時間。而服務(wù)端渲染從用戶發(fā)出一次頁面 url 請求之后,應(yīng)用服務(wù)器返回的 html 字符串就是完備的計算好的,可以交給瀏覽器直接渲染,使得 DOM 的渲染不再受靜態(tài)資源和 ajax 的限制。

          Q2:服務(wù)端渲染有哪些限制?

          A:比較常見的限制比如:

          1. 因為渲染過程是在 Node 端,所以沒有 DOM 和 BOM 對象,因此不要在常見的 Vue 的 beforeCreate 和 created 生命周期鉤子里做涉及 DOM 和 BOM 的操作
          2. 對第三方庫的要求比較高,如果想直接在 Node 渲染過程中調(diào)用第三方庫,那這個庫必須支持服務(wù)端渲染

          Q3:如果我的需求只是生成文案類的靜態(tài)頁面,需要用到服務(wù)端渲染嗎?

          A:像這些和用戶狀態(tài)無關(guān)的靜態(tài)頁面,完全可以采用預(yù)渲染的方式(具體見 Vue SSR 官方指南),服務(wù)端渲染適用的更多場景會是狀態(tài)相關(guān)的(比如用戶信息相關(guān)),需要經(jīng)過 CPU 計算才能輸出完備的 html 字符串,因此服務(wù)端渲染是一個 CPU 密集型的操作。而靜態(tài)頁面完全不需要涉及任何復(fù)雜計算,通過預(yù)渲染更快且更節(jié)省 CPU 資源。

          、為什么xml

          需要服務(wù)器端返回少量的、單一的數(shù)據(jù)

          用戶名是否可用 1 / 0

          返回兩個數(shù)的和 400

          登錄是否成功 true/false

          數(shù)據(jù)插是否成功 true/false

          如果我們需要從服務(wù)器端返回大量、復(fù)雜的數(shù)據(jù),如何實現(xiàn)?

          xml:服務(wù)器端返回xml數(shù)據(jù)

          json:服務(wù)器端返回json數(shù)據(jù)

          2、格式:

          (1)php解析xml

          $dom=new DOMDocument();
          $dom->loadXML($str);
          $nd=$dom->getElementsByTagName("TagName");
          $value=$nd->item(0)->nodeValue
          $xml=simplexml_load_string($str);
          $first=$xml->first;
          $second=$xml->second;

          (2)js解析xml

          var xml=xmlHttp.responseXML;
          node=xml.getElementsByTagName("TagName");
          node[0].childNodes[0].nodeValue;

          3 、案例1:

          實現(xiàn)兩個數(shù)的四則運(yùn)算

          HTML代碼:

          <script language="javascript" src="public.js"></script>
          <script>
          window.onload=function(){
                  $('btnOk').onclick=function(){
                  var f=$('first').value;
                  var s=$('second').value;
                  var data='first='+f+'&second='+s;
                  var xhr=createxhr();
                  xhr.open('post','demo01.php');
                  xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
                  xhr.onreadystatechange=function(){
                          if(xhr.readyState==4 && xhr.status==200){
                                  //xml --->xml dom對象
                                  var xml=xhr.responseXML;
                                  var str=xml.getElementsByTagName('jia')[0].childNodes[0].nodeValue;
                                  str+='|'+xml.getElementsByTagName('jian')[0].childNodes[0].nodeValue;
                                  str+='|'+xml.getElementsByTagName('cheng')[0].childNodes[0].nodeValue;
                                  str+='|'+xml.getElementsByTagName('chu')[0].childNodes[0].nodeValue;
                                  $('result').innerHTML=str;
                          }
                  };
                  xhr.send(data);
                  };
          };
          </script>
          <input type="text" id="first" /><br>
          <input type="text" id='second' /><br>
          <div id='result'></div>
          <input type="button" id="btnOk" value="計算" />

          理解:

          var xml=xhr.responseXML; 得到ajax返回的xmldom對象

          xml.getElementsByTagName('jia')[0] :是表示獲取jia這個元素

          xml.getElementsByTagName('jia')[0].childNodes:表示獲取jia元素下的所有子節(jié)點

          xml.getElementsByTagName('jia')[0].childNodes[0] :表示獲取jia元素下的唯一文本節(jié)點

          xml.getElementsByTagName('jia')[0].childNodes[0].nodeValue:文本節(jié)點的值

          php代碼:

          <?php
          $first=$_POST['first'];
          $second=$_POST['second'];
          $result1=$first+$second;
          $result2=$first-$second;
          $result3=$first*$second;
          $result4=$first/$second;
          //要想返回xml,首先連接一個xml格式的字符串
          $str='<root>';
          $str.='<jia>'.$result1.'</jia>';
          $str.='<jian>'.$result2.'</jian>';
          $str.='<cheng>'.$result3.'</cheng>';
          $str.='<chu>'.$result4.'</chu>';
          $str.='</root>';
          /*$str=<<<str
          <root>
          <jia>$result1</jia>
          </root>
          str;*/
          header('Content-type:text/xml');
          echo $str;

          理解:

          得到結(jié)果后,需要使用字符串連接成一個xml格式的字符串,如:需要一個根元素,下面子元素,最后是具體的值,

          連接時也可以使用<<<str創(chuàng)建xml字符串

          str;

          輸出這個字符串時,默認(rèn)的響應(yīng)內(nèi)容類型:text/html,也就是說客戶端仍把代碼當(dāng)做html來進(jìn)行解析,

          ajax對象的responeXML是不能得到一個xmldom對象,必須設(shè)置響應(yīng)頭類型為:text/xml,其代碼:header('Content-type:text/xml');

          public.js:

          function createxhr() {
                  /*var xhr;
                  var str=window.navigator.userAgent;
                  if (str.indexOf('MSIE') > 0) {
                  xhr=new ActiveXObject('Microsoft.XMLHTTP');
                  } else {
                  xhr=new XMLHttpRequest();
                  }
                  return xhr;*/
          
                  try{return new XMLHttpRequest();}catch(e){}
                  try{return new ActiveXObject('Microsoft.XMLHTTP'); }catch(e){}
                  alert('請更換瀏覽器!');
                  }
                  function $(id){
                  return document.getElementById(id);
          }

          4、案例2

          在頁面加載之后,將mysql數(shù)據(jù)庫goods表中所有數(shù)據(jù)顯示在表格中

          <root>
          <goods>
          <name>222</name>
          <price>55.00</price>
          </goods>
          <goods>
          <name>諾 E661</name>
          <price>205.00</price>
          </goods>
          <goods>
          <name>諾 E661</name>
          <price>200.00</price>
          </goods>
          </root>

          HTML代碼:

          <style>
          tr{
          background-color:#ffffff;
          height:30px;
          font-size:12px;
          }
          </style>
          <script language="javascript" src='public.js'></script>
          <script>
          window.onload=function(){
                  var xhr=createxhr();
                  xhr.open('post','demo02.php');
                  xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
                  xhr.onreadystatechange=function(){
                          if(xhr.readyState==4 && xhr.status==200)
                                  var xml=xhr.responseXML;
                                  var goods=xml.getElementsByTagName('goods');
                                  for(var i=0;i<goods.length;i++){
                                  //創(chuàng)建行元素
                                  var tr=document.createElement('tr');
                                  //創(chuàng)建序號td元素
                                  var tdID=document.createElement('td');
                                  tdID.innerHTML=i+1;
                                  //創(chuàng)建名稱td元素
                                  var tdName=document.createElement('td');
                                  tdName.innerHTML=goods[i].childNodes[0].childNodes[0].nodeValue;
                                  //創(chuàng)建價格td元素
                                  var tdPrice=document.createElement('td');
                                  tdPrice.innerHTML=goods[i].childNodes[1].childNodes[0].nodeValue;
                                  //將三個td追加到tr元素
                                  tr.appendChild(tdID);
                                  tr.appendChild(tdName);
                                  tr.appendChild(tdPrice);
                                  document.getElementsByTagName('TBODY')[0].appendChild(tr);
                          }
          };
          xhr.send(null);
          }
          </script>
          <table id='tbData' width="800" cellspacing="1" cellpadding="4" bgcolor="#336699">
          <tr>
          <td>序號</td>
          <td>商品名稱</td>
          <td>商品價格</td>
          </tr>
          </table>

          理解:

          創(chuàng)建行元素,

          創(chuàng)建單元格元素

          將單元格元素追加到行元素中

          將行元素追加到表格元素中

          php代碼:

          <?php
                  //查詢goods表中所有數(shù)據(jù)并返回
                  $sql="select name,price from goods order by id desc";
                  mysql_connect('localhost','root','111111');
                  mysql_select_db('shop');
                  mysql_query('set names gb2312');
                  $result=mysql_query($sql); //發(fā)送sql語句
                  $num=mysql_num_rows($result); //總行數(shù)
                  $str='<root>';
                  for($i=0;$i<$num;$i++){
                  $row=mysql_fetch_assoc($result);
                  $str.='<goods>';
                  $str.='<name>'.iconv('gb2312','utf-8',$row['name']).'</name>';
                  $str.='<price>'.$row['price'].'</price>';
                  $str.='</goods>';
                  }
                  $str.='</root>';
                  header('Content-Type:text/xml');
                  echo $str;
          ?>

          理解:

          查詢goods表中所有數(shù)據(jù)

          連接xml格式的字符串

          表中有多少條數(shù)據(jù)

          xml字符串就有幾對goods標(biāo)簽

          其中, name字段出現(xiàn)中文,所以需要進(jìn)行轉(zhuǎn)碼 gb2312--utf-8

          最后, 輸出xml字符串

          譯 | 核子可樂、Tina

          技術(shù)和軟件開發(fā)領(lǐng)域存在一種有趣的現(xiàn)象,就是同樣的模式迭起興衰、周而復(fù)始。

          htmx 的走紅

          過去Web非常簡單。URL 指向服務(wù)器,服務(wù)器將數(shù)據(jù)混合成 html,然后在瀏覽器上呈現(xiàn)該響應(yīng)。圍繞這種簡單范式,誕生了各種Javascript框架,以前可能需要數(shù)月時間完成的一個應(yīng)用程序基本功能,現(xiàn)在借助這些框架創(chuàng)建相對復(fù)雜的項目卻只需要數(shù)小時,我們節(jié)省了很多時間,從而可以將更多精力花在業(yè)務(wù)邏輯和應(yīng)用程序設(shè)計上。

          但隨著 Web 不斷地發(fā)展,Javascript 失控了。不知何故,我們決定向用戶拋出大量 App,并在使用時發(fā)出不斷增加的網(wǎng)絡(luò)請求;不知何故,為了生成 html,我們必須使用 JSON,發(fā)出數(shù)十個網(wǎng)絡(luò)請求,丟棄我們在這些請求中獲得的大部分?jǐn)?shù)據(jù),用一個越來越不透明的 JavaScript 框架黑匣子將 JSON 轉(zhuǎn)換為 html,然后將新的 html 修補(bǔ)到 DOM 中......

          難道大家快忘記了我們可以在服務(wù)器上渲染 html 嗎?更快、更一致、更接近應(yīng)用程序的實際狀態(tài),并且不會向用戶設(shè)備發(fā)送任何不必要的數(shù)據(jù)?但是如果沒有 Javascript,我們必須在每次操作時重新加載頁面。

          現(xiàn)在,有一個新的庫出現(xiàn)了,摒棄了定制化的方法,這就是 htmx。作為 Web 開發(fā)未來理念的一種實現(xiàn),它的原理很簡單:

          • 從任何用戶事件發(fā)出 AJAX 請求。
          • 讓服務(wù)器生成代表該請求的新應(yīng)用程序狀態(tài)的 html。
          • 在響應(yīng)中發(fā)送該 html。
          • 將該元素推到它應(yīng)該去的 DOM 中。

          htmx 出現(xiàn)在 2020 年,創(chuàng)建者 Carson Gross 說 htmx 來源自他于 2013 年研究的一個項目 intercooler.js。2020 年,他重寫了不依賴 jQuery 的 intercooler.js,并將其重命名為 htmx。然后他驚訝的發(fā)現(xiàn) Django 社區(qū)迅速并戲劇性地接受了它!

          圖片來源:https://lp.jetbrains.com/django-developer-survey-2021-486/

          Carson Gross 認(rèn)為 htmx 設(shè)法抓住了開發(fā)者對現(xiàn)有 Javascript 框架不滿的浪潮,“這些框架非常復(fù)雜,并且經(jīng)常將 Django 變成一個愚蠢的 JSON 生產(chǎn)者”,而 htmx 與開箱即用的 Django 配合得更好,因為它通過 html 與服務(wù)器交互,而 Django 非常擅長生成 html。

          對于 htmx 的迅速走紅,Carson Gross 發(fā)出了一聲感嘆:這真是“十年窗下無人問,一舉成名天下知(this is another example of a decade-long overnight success)”。

          htmx 的實際效果

          可以肯定的一點是 htmx 絕對能用,單從理論上講,這個方法確實值得稱道。但軟件問題終究要歸結(jié)于實踐效果:效果好嗎,能不能給前端開發(fā)帶來改善?

          在 DjangoCon 2022 上,Contexte 的 David Guillot 演示了他們在真實 SaaS 產(chǎn)品上實現(xiàn)了從 React 到 htmx 的遷移,而且效果非常好,堪稱“一切 htmx 演示之母”(視頻地址:https://www.youtube.com/watch?v=3GObi93tjZI)

          Contexte 的項目開始于 2017 年,其后端相當(dāng)復(fù)雜,前端 UI 也非常豐富,但團(tuán)隊非常小。所以他們在一開始的時候跟隨潮流選擇了 React 來“構(gòu)建 API 綁定 SPA、實現(xiàn)客戶端狀態(tài)管理、前后端狀態(tài)分離”等。但實際應(yīng)用中,因為 API 設(shè)計不當(dāng),DOM 樹太深,又需要加載很多信息,導(dǎo)致 UI“非常非常緩慢”。在敏捷開發(fā)的要求下,團(tuán)隊里唯一的 Javascript 專家對項目的復(fù)雜性表現(xiàn)得一無所措,因此他們決定試試 htmx。

          九大數(shù)據(jù)提升

          于是我們決定大膽嘗試,花幾個月時間用簡單的 Django 模板和 htmx 替換掉了 SaaS 產(chǎn)品中已經(jīng)使用兩年的 React UI。這里我們分享了一些相關(guān)經(jīng)驗,公布各項具體指標(biāo),希望能幫同樣關(guān)注 htmx 的朋友們找到說服 CTO 的理由!

          1. 這項工作共耗費(fèi)了約 2 個月時間(使用 21K 行代碼庫,主要是 JavaScript)
          2. 不會降低應(yīng)用程序的用戶體驗(UX)
          3. 將代碼庫體積減小了 67%(由 21500 行削減至 7200 行)
          4. 將 Python 代碼量增加了 140%(由 500 行增加至 1200 行);這對更喜歡 Python 的開發(fā)者們應(yīng)該是好事
          5. 將 JS 總體依賴項減少了 96%(由 255 個減少至 9 個)

          6. 將 Web 構(gòu)建時間縮短了 88%(由 40 秒縮短至 5 秒)

          7. 首次加載交互時間縮短了 50% 至 60%(由 2 到 6 秒,縮短至 1 到 2 秒)

          8. 使用 htmx 時可以配合更大的數(shù)據(jù)集,超越 React 的處理極限

          9. Web 應(yīng)用程序的內(nèi)存使用量減少了 46%(由 75 MB 降低至 40 MB)

          這些數(shù)字令人頗為意外,也反映出 Contexte 應(yīng)用程序高度契合超媒體的這一客觀結(jié)果:這是一款以內(nèi)容為中心的應(yīng)用程序,用于顯示大量文本和圖像。很明顯,其他 Web 應(yīng)用程序在遷移之后恐怕很難有同樣夸張的提升幅度。

          但一些開發(fā)者仍然相信,大部分應(yīng)用程序在采用超媒體 /htmx 方法之后,肯定也迎來顯著的改善,至少在部分系統(tǒng)中大受裨益。

          開發(fā)團(tuán)隊組成

          可能很多朋友沒有注意,移植本身對團(tuán)隊結(jié)構(gòu)也有直接影響。在 Contexte 使用 React 的時候,后端與前端之間存在硬性割裂,其中兩位開發(fā)者全職管理后端,一位開發(fā)者單純管理前端,另有一名開發(fā)者負(fù)責(zé)“全棧”。(這里的「全棧」,代表這位開發(fā)者能夠輕松接手前端和后端工作,因此能夠在整個「棧」上獨立開發(fā)功能。)

          而在移植至 htmx 之后,整個團(tuán)隊全都成了“全棧”開發(fā)人員。于是每位團(tuán)隊成員都更高效,能夠貢獻(xiàn)出更多價值。這也讓開發(fā)變得更有樂趣,因為開發(fā)人員自己就能掌握完整功能。最后,轉(zhuǎn)向 htmx 也讓軟件優(yōu)化度上了一個臺階,現(xiàn)在開發(fā)人員可以在棧內(nèi)的任意位置進(jìn)行優(yōu)化,無需與其他開發(fā)者提前協(xié)調(diào)。

          htmx 是傳統(tǒng)思路的回歸

          如今,單頁應(yīng)用(SPA)可謂風(fēng)靡一時:配合 React、Redux 或 Angular 等庫的 JS 或 TS 密集型前端,已經(jīng)成為創(chuàng)建 Web 應(yīng)用程序的主流方式。以一個需要轉(zhuǎn)譯成 JS 的 SPA 應(yīng)用為例:

          但 htmx 風(fēng)潮已經(jīng)襲來,人們開始強(qiáng)調(diào)一種“傻瓜客戶端”方法,即由服務(wù)器生成 html 本體并發(fā)送至客戶端,意味著 UI 事件會被發(fā)送至服務(wù)器進(jìn)行處理。

          用這個例子進(jìn)行前后對比,我們就會看到前者涉及的活動部件更多。從客戶端角度出發(fā),后者其實回避了定制化客戶端技術(shù),采取更簡單的方法將原本只作為數(shù)據(jù)引擎的服務(wù)器變成了視圖引擎。

          后一種方法被稱為 AJAX(異步 JavaScript 與 XML)。這種簡單思路能夠讓 Web 應(yīng)用程序獲得更高的響應(yīng)性體驗,同時消除了糟糕的“回發(fā)”(postback,即網(wǎng)頁完全刷新),由此回避了極其低效的“viewstate”等.NET 技術(shù)。

          htmx 在很多方面都體現(xiàn)出對 AJAX 思路的回歸,最大的區(qū)別就是它僅僅作為新的聲明性 html 屬性出現(xiàn),負(fù)責(zé)指示觸發(fā)條件是什么、要發(fā)布到哪個端點等。

          另一個得到簡化的元素是物理應(yīng)用程序的結(jié)構(gòu)與構(gòu)建管道。因為不再涉及手工編寫 JS,而且整個應(yīng)用程序都基于服務(wù)器,因此不再對 JS 壓縮器、捆綁器和轉(zhuǎn)譯器做(即時)要求。就連客戶端項目也能解放出來,一切都由 Web 服務(wù)器項目負(fù)責(zé)完成,所有應(yīng)用程序代碼都在.NET 之上運(yùn)行。從這個角度來看,這與高度依賴服務(wù)器的 Blazor Server 編程模型倒是頗有異曲同工之妙。

          技術(shù)和軟件開發(fā)領(lǐng)域存在一種有趣的現(xiàn)象,就是同樣的模式迭起興衰、周而復(fù)始。隨著 SPA 的興起,人們一度以為 AJAX 已經(jīng)過氣了,但其基本思路如今正卷土重來。這其中當(dāng)然會有不同的權(quán)衡,例如更高的服務(wù)器負(fù)載和網(wǎng)絡(luò)流量(畢竟現(xiàn)在我們發(fā)送的是數(shù)據(jù)視圖,而不只是數(shù)據(jù)),但能讓開發(fā)者多個選擇肯定不是壞事。

          雖然不敢確定這種趨勢是否適用于包含豐富用戶體驗的高復(fù)雜度應(yīng)用程序,但毫無疑問,相當(dāng)一部分 Web 應(yīng)用程序并不需要完整的 SPA 結(jié)構(gòu)。對于這類用例,簡單的 htmx 應(yīng)用程序可能就是最好的解決方案。

          參考鏈接:

          https://news.ycombinator.com/item?id=33218439

          https://www.reddit.com/r/django/comments/rxjlc6/htmx_gaining_popularity_rapidly/

          https://mekhami.github.io/2021/03/26/htmx-the-future-of-web/

          https://www.compositional-it.com/news-blog/more-on-htmx-back-to-the-future/

          聲明:本文為InfoQ編譯,未經(jīng)許可禁止轉(zhuǎn)載。


          主站蜘蛛池模板: 精品一区二区91| 国精品无码一区二区三区在线蜜臀| 在线日产精品一区| 国产精品无码亚洲一区二区三区| 亚洲AV无码一区二区三区电影 | 综合无码一区二区三区四区五区| 亚洲大尺度无码无码专线一区| 国产成人精品无码一区二区老年人| av无码精品一区二区三区四区| 亚洲AV成人一区二区三区AV| 亚洲AV无码一区二区乱子仑| 亚洲片国产一区一级在线观看| 91精品一区二区三区久久久久| 国产福利在线观看一区二区| 中文字幕VA一区二区三区| 精品视频一区二区三三区四区| 日韩一区在线视频| 2014AV天堂无码一区| 色一情一乱一区二区三区啪啪高| 国产精品一区二区久久精品无码| 成人精品一区二区户外勾搭野战| 午夜无码视频一区二区三区| 精品视频一区二区三三区四区| 成人在线一区二区| 亚洲av日韩综合一区二区三区| 亚洲一区二区三区偷拍女厕| 精品一区二区久久久久久久网精| 精品一区二区久久久久久久网站| 怡红院美国分院一区二区| 日韩人妻一区二区三区蜜桃视频| 天天爽夜夜爽人人爽一区二区| 日本一区二区视频| 国精品无码一区二区三区在线| 国产亚洲福利一区二区免费看 | 色一情一乱一伦一区二区三欧美 | 韩国女主播一区二区| 精品人妻AV一区二区三区| 夜夜添无码试看一区二区三区| 国产日韩精品一区二区在线观看播放| 亚洲一区二区三区四区视频| 内射女校花一区二区三区|