5分鐘上手Egg.js+nunjucks模板引擎快速開發SEO友好的官網項目

来源:https://www.cnblogs.com/coderhf/archive/2020/06/30/13213975.html
-Advertisement-
Play Games

在日常的項目中,有時候還是不可避免的會維護一些jq官網項目等。面對此類需求,很多還是以前的老套路,前端寫頁面交給後端去套數據。很煩有木有~~而改動之後還得交給後端再次修改,時間和溝通都是個麻煩。同時開發中,寫靜態頁面也很麻煩,不能復用,不能使用現在的工具,心累有木有~~當然了,解決辦法很多 自己寫個 ...


在日常的項目中,有時候還是不可避免的會維護一些jq官網項目等。面對此類需求,很多還是以前的老套路,前端寫頁面交給後端去套數據。很煩有木有~~而改動之後還得交給後端再次修改,時間和溝通都是個麻煩。同時開發中,寫靜態頁面也很麻煩,不能復用,不能使用現在的工具,心累有木有~~當然了,解決辦法很多

  • 自己寫個webpack腳本維護起來,把工程化的那一套東西搬過來。

  • 使用現成的nust\next等服務端渲染框架

  • 藉助於node+模板引擎等

  • ...

而本文介紹一下node的egg.js框架配合模板引擎來快速開發項目的模式。上手簡單快速,一個人搞定前後端。PS:又可以學習新知識來,我好(草)開(泥)心(馬),奧利給~~~

 

老羅扇臉圖.gif

 

初始化項目

 # 初始化
 cnpm init egg --type=simple
 
 # 安裝依賴
 cnpm i
 
 # 啟動服務
 npm run dev

簡單看下生成的文件目錄(ps:個別文件是沒有的,後期自己添加的)

 

目錄.jpg

 

基本介紹

先介紹一下egg中app下的這些文件的基本作用,有個大概的概念:

  • app/router.js中編寫路由

  • app/controller/下編寫對應的控制器

  • app/service/下編寫service

  • app/view/下編寫對應的模板

 

一臉懵逼圖

 

路由編寫

  • 路由,類似前端項目中的路由。是用來定義請求的URL。

  • 當用戶訪問該路由地址時,將映射到對應的controller進行進一步的處理。

  • 由此可見,路由router就是定義和controller之間的一種映射關係。

定義路由,首先要打開app/router.js文件,在裡面進行定義,例如:

 'use strict';
 ​
 module.exports = app => {
   const { router, controller } = app;
   // 定義首頁的路由
   // 即當直接訪問功能變數名稱的時候,將請求映射到controller.home.home中進行進一步的處理
   router.get('/', controller.home.home);
   // 定義關於我們的路由
   router.get('/about', controller.home.about);
   // 定義新聞詳情的路由
   router.get('/details/:id', controller.home.details);
 };

 

  • 此處的路由可以理解為就是我們訪問的功能變數名稱後面的具體的路徑地址。例如xxx.com/about中的/about

  • controller.xx.xx是指當我們訪問了這個路由的時候,服務將當前路由映射到這個控制器中。具體的控制器作用,下麵會詳細介紹。

控制器Controller

  • 控制器,主要的作用就是處理用戶請求的參數,然後可以調用service服務得到我們需要的數據,最後返回數據或者直接渲染出一個模板。

  • 而我們這裡則是根據router(router參數非必須)渲染出一個頁面,也就是渲染一個html頁面返回,這樣瀏覽器打開的就直接是頁面了。

 'use strict';
 ​
 const Controller = require('egg').Controller;
 ​
 class HomeController extends Controller {
   async home() {
     const { ctx } = this;
     await ctx.render('index.njk')
   }
 }
 ​
 module.exports = HomeController
 ​
 // 或者你也可以簡化一下寫法
 module.exports = class extends require('egg').Controller {
     // ...
 }

 

  • 通過調用ctx.render('index.njk')方法,我們將渲染一個模板並返回(可以理解為返回給瀏覽器)

  • 在渲染模板的時候我們也可以給模板傳遞一個數據對象,ctx.render('index.njk', {}),然後模板內就可以通過模板引擎的語法渲染我們的數據了。

  • 一般這個數據是通過service讀取的資料庫或者調用其他介面得到的數據。這個會在下麵的service中講到。

下麵演示一個調用service的例子:

 // app/controller/home.js
'use strict';
 module.exports = class extends require('egg').Controller {
     async details() {
     const { ctx } = this;
     try {
         // 調用service的home模塊中的details服務
         // 得到數據後,塞給靜態模板使用
         const data = await ctx.service.home.details(ctx.params.id)
         // 渲染一個模板
         await ctx.render('details.njk', data)
     } catch (error) {
       ctx.body = '新聞獲取出錯'
     }
   }
 }

 


 
  • 關於模板相關的內會在下麵詳細講解

  • 關於service的定義和使用接下來馬上講解

Service服務

service服務主要是用來做什麼的呢?上面在controller中也提到了,主要用來獲取數據,拿到數據之後也可以格式化再返回給controller使用。

下麵演示一個service調用某些介面服務得到數據並返回給controller使用:

 // 首先需要在app/下新建service文件夾
 // 然後在service下新建home.js,最終為app/service/home.js
'use strict';
 ​
 module.exports = class extends require('egg').Service {
     // 根據文章id獲取文章數據
     // 此處的id是controll調用service服務時傳遞的id
     async details(id) {
         try {
             const url = `https:xxxx.com/api/${id}`
             const { data } = await this.ctx.curl(url, { dataType: 'json' })
             if (data.data && data.code === 200) {
                 // 此處也可以對數據進行處理後再返回
                 
                 // 返回數據
                 return data.data
             }
             throw '數據獲取失敗'
         } catch (error) {
           throw error
         }
     }
 ​
 }

 

  • 之所以叫home.js,是因為我們controller中使用的是ctx.service.home

  • service.home中的home就是service文件的文件名稱

  • 此處進行了最簡單的數據獲取,通過this.ctx.curl方法請求其他介面的數據。類似於使用axios獲取數據的操作。

  • 當然了,也可以進行資料庫數據的增刪改查操作,本次就不做演示了,後面會考慮新開一篇egg做項目開發的文章再詳細介紹吧。

  • 此處對於介面的功能變數名稱配置可以放在配置文件中

  • 對於curl方法也可以進一步的封裝在helper中

  • 對於數據返回的攔截也可以封裝在中間件進行攔截

  • 對於上面這些內容有興趣的可以翻看egg文件中相應的說明,還是蠻詳細的。

模板渲染

 

nunjucks.jpg

 

定義好了基本的路由、控制器和service之後,就剩下模板了。首先模板,可能是前端小伙伴寫的靜態頁面給到我們的(ps:這個前端充氣小伙伴或許就是我們自己!哦呸,不是充氣的)

好了,言歸正傳!Egg中有關於模板渲染的文檔,可以看一下。Egg本身內置了egg-view作為模板的解決方案,其中View支持插件egg-view-nunjucks,本文也是通過該插件進行的模板開發。

 # 首先安裝
 cnpm i egg-view-nunjucks --save
 ​
 # 然後在根目錄下的config/plugin.js中使用插件
 'use strict';
 ​
 module.exports = {
   nunjucks: {
     enable: true,
     package: 'egg-view-nunjucks',
   }
 };
 ​
 # 完成了插件的安裝和引入,我們還需要配置插件的參數
 # 根目錄下的config/config.default.js中
 module.exports = appInfo => {
     // 其他代碼
     // ...
     
     // 配置我們的插件參數
     config.view = {
         // 定義映射的文件尾碼名
         // 此處我們定義為.njk,那麼我們的模板都需要以.njk結束,
         // 這樣該類型的文件才會被我們的模板插件進行處理
         mapping: {
           '.njk': 'nunjucks',
         }
     }
 ​
     // 其他代碼
     // ...
 }

 

  • 註意,如果不叫.njk也是可以的,這個是自定義的,只要滿足你的模板文件的尾碼名和你定義的一樣就行,這樣才會被插件處理。

  • egg-view-nunjucks封裝的是nunjucks,nunjucks推薦叫.njk

egg-view-nunjucks文檔 nunjucks文檔

有了模板引擎,我們嵌套數據等就會方便很多了。下麵來簡單看下一個模板的文件夾:

 

模板文件夾.jpg

 

  • 如上圖所示,需要新建app/view文件夾,所有的模板放在該文件夾下。這個也是可以通過配置修改的(如果你有需求的話);

  • 上面的模板文件,根據你的項目來,這裡僅作為演示。通常的,我們可能會把頁面一些通用的頭部啊、底部、菜單等單獨抽離處理復用,具體的還是看你的項目。

下麵我們簡單看下base.njk這個模板,做了什麼事情:

 <!DOCTYPE html>
 <html lang="zh-CN">
   <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
     <title>{% block title %}title預設內容{% endblock %}</title>
     <meta name="keywords" content="{% block keywords %}keywords預設內容{% endblock %}">
     <meta name="description" content="{% block description %}description預設內容{% endblock %}"><link rel="stylesheet" type="text/css" href="../../public/css/base.css">
     {% block head %}{% endblock %}
   </head><body>
     <!-- 主體內容部分 -->
     {% block content %}{% endblock %}
 ​
     <script src="../../public/js/jquery.js"></script>
     <!-- script部分 -->
     {% block script %}{% endblock %}
   </body>
 </html>

 

  • 很多時候,靜態頁面基本的結構都是一樣的,或者說類似的。所以我們可以定義一個基本的模板,然後所有的頁面繼承我們這個基礎的模板就好。如何繼承,後面會講。

  • 比如,這裡我們定義的基本模板中有頭部,但是頭部中的title等內容可能各個頁面不一樣,所以我們自定義一個block塊,這樣就可以在頁面中替換掉這塊的內容。有些類似於vue的slot插槽。

 // 定義block塊
 {% block title %}預設內容{% endblock %}
  • 簡單說一下這裡個人定義的幾個基本的block的作用吧:

名稱說明
head塊 用於在head內最底部插入一些代碼。例如,公共模板引入了公共css,但是每個頁面還有可能單獨引入css,全局只有一個css文件的除外
content塊 用於放置每個頁面的主體部分
script塊 用於放置每個頁面需要單獨引入的js文件

定義好了基本的模板和塊,下麵我們看下頁面中如何使用:

 {% extends "./base/base.njk" %}
 ​
 {% block title %}這是一個新的title{% endblock %}
 {% block description %}這是一個新的description{% endblock %}
 {% block keywords %}壹沓科技,加入壹沓,聯繫我們{% endblock %}
 {% set navActive = "about" %}
 ​
 {% block head %}
   <link rel="stylesheet" href="../public/css/swiper.min.css">
 {% endblock %}
 ​
 {% block content %}
   <div class="page-wrapper">
     <!-- 導入公共的nav模板 -->
     {% include './base/nav.njk' %}
 ​
     <!-- 背景圖 -->
     <section class="banner-wrapper">
       <img src="../public/img/icon/about-banner.jpg" alt="背景LOGO">
       <span>{{ userName }}</span>
     </section>
     
     <!-- 渲染html模板演示 -->
     <div>{{content | safe}}</div>
<!-- 導入公共的底部模板 -->
     {% include './base/foot.njk' %}
   </div>
 {% endblock %}
 ​
 {% block script %}
   <script src="../public/js/swiper.min.js"></script>
   <script src="../public/js/about.js"></script>
 {% endblock %}

 

  • 通過上面一個簡單的模板,卻包含了最常用和最關鍵的很多特性。首先第一行,我們要讓當前頁面繼承我們定義的基礎模板:

 // 相對路徑即可
 {% extends "./base/base.njk" %}
  • 覆蓋我們在基礎模板中自定義的塊。也可以理解為給vue的slot傳入新的內容:

 // 例如,設置該頁面的title
 // 如果不設置,就會使用基礎模板中預設的
 {% block title %}這是一個新的title{% endblock %}
  • 設置變數,在當前模板中都可以使用。包括引入的其他模板,只要是出現在了當前模板中,都可以取到該值進行使用。

 // 定義了一個navActive變數
 // 後面會講解一個常見的場景
 {% set navActive = "about" %}
  • 使用變數、渲染內容/html

 // 變數的使用,和vue的{{}}插值一樣
 // 比如這個userName是我們從controller中調用service獲取的數據對象中的一個屬性,
 // 然後把對象傳遞給了模板,
 // 那麼在模板中可以直接取對象中的屬性進行渲染
 // 註意,傳遞給模板的是一個對象,例如{userName: 'jack'},但是我們使用的時候直接取userName
 {{userName}}
 
 
 // 同樣的,我們在模板內定義的變數也可以直接使用的
 
 // 渲染html,比如很常見的富文本
 // 類似vue的過濾器,這個safe是內置的,
 // 過濾器也可以自定義,具體的查看文檔吧
 {{content | safe}}
  • 引入其他模板。比如上面可以看到,我們繼承了基礎的模板,但是我們還可以會提取一些公共的像nav模板進行復用。

 // 導入我們的nav模板
 {% include './base/nav.njk' %}

 

  • nav模板一般很常見的會有這麼一個場景,比如就是當前頁面選項需要高亮。來,先看一下nav模板:

 <!-- 導航頭部 -->
 <nav class="nav-wrapper">
   <ul class="menu-content">
     <li>
       <a href="/news" class="{{ 'active' if navActive == 'news' else '' }}">動態資訊</a>
     </li>
     <li>
       <a href="/about" class="{{ 'active' if navActive == 'about' else '' }}">關於我們</a>
     </li>
   </ul>
 </nav>

 

可以看到,我們對class名進行了一個if判斷,判斷當變數navActive是某些值的時候給他增加一個active類名,否則就是各空的class名。這裡仔細註意一下寫法即可。 那麼我們怎麼定義變數呢?此時再回到上面定義變數的部分介紹,我們已經在頁面中定義了一個變數navActive,所以我們只需要每個頁面定義一個navActive且值為我們需要的即可了。

  • 最後再補充說明一下,模板內的調整路徑,就直接寫成我們在router.js中定義的路由即可,而不是寫的模板地址。

 // a標簽跳轉
 <a href="/news"></a>
// js調整也是一樣,使用router.js定義的路由
 window.location.href="/news"

 

總結

文章是有點水,請輕噴

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 項目搭建時間:2020-06-29 本章節:講述基於vue/cli, 項目的基礎搭建。 本主題講述了vue+element-ui JYAdmin 後臺管理系統模板-集成方案,從零到一的手寫搭建全過程。 該項目不僅是一個持續完善、高效簡潔的後臺管理系統模板,還是一套企業級後臺系統開發集成方案,致力於打 ...
  • //商品列表 var id=@Session["Id"]; function load() { $.ajax({ url: "http://localhost:52975/api/Goods/GetGood/"+id, type: "post", dataType: "json", success: ...
  • 在前面隨筆《循序漸進VUE+Element 前端應用開發(12)--- 整合ABP框架的前端登錄處理》介紹了一個系統最初接觸到的前端登錄處理的實現,但往往對整個系統來說,一般會有很多業務對象,而每個業務對象的API介面又有很多,不過簡單來說也就是常規的增刪改查,以及一些自定義的介面,通用都比較有規律... ...
  • 摘要 頁面報錯bug是常有的事,我們可以根據錯誤bug提示關鍵性的去修複問題。依稀的記得之前有個小伙伴詢問了一個js異常的錯誤bug:Uncaught TypeError: xxx is not a function。這個錯誤問題定位處理起來很快,但是我卻不知其所以然。為了弄清楚報錯的深層面原因,去 ...
  • Web前端作為開發團隊中不可或缺的一部分,需要按照相關規定進行合理編寫(一部分不良習慣可能給自己和他人造成不必要的麻煩)。不同公司不同團隊具有不同的規範和文檔。下麵是根據不同企業和團隊的要求進行全面詳細的整理結果。備註:實際開發請以本公司的規範為標準。 A.基本原則 符合web標準(UTF-8,HT ...
  • 1.格式化金錢值 const ThousandNum = num => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); const money = ThousandNum(20190214); // money => "20,190,214 ...
  • 摘要 近期在優化團隊代碼,發現Redux重覆使用的代碼過多。 經過調研發現了Rematch庫:Redux是一個出色的狀態管理工具,並且有著健全的中間件生態以及出色的開發工具;Rematch是沒有boilerplate的Redux最佳實踐。移除了聲明action類型、action創建函數、thunks ...
  • safari瀏覽器字體不能自動隨網頁縮放調整大小 -webkit-text-size-adjust:100% 點擊<button><input>有灰色透明背景 -webkit-tap-highlight-color:rgba(0,0,0,0); 微信、QQ內置瀏覽器視頻自動全屏 非騰訊功能變數名稱的視頻地址 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...