原文連結:blog.logrocket.com/exploring-s…
因為英文水平有限,有問題大家留言批評。本來準備結合作者文章做體驗文章,但是覺得感覺作者對於sapper的初始結構介紹很全面,沒必要畫蛇添足。
在戰爭中,建造橋樑,修路,清理雷區並進行拆除(在戰鬥條件下均如此)的士兵被稱為工兵(Sapper)。
我們可以單獨使用Svelte構建更復雜的應用程式,但是隨著程式碼邏輯深入,它可能很快就會變得混亂。那麼讓我們來看看Sapper!
簡介
Sapper是Svelte的配套元件框架,可幫助您以快速有效的方式構建更大,更復雜的應用程式。
在當今時代,構建web應用程式是一項相當複雜的工作,包括程式碼分解、資料管理、效能優化等。這就是為什麼今天有無數的前端工具,但它們都有自己的複雜性和學習曲線。
開發一個應用程式應該不會那麼困難,對吧?它能比現在更簡單嗎?有沒有一種方法能讓你在保持頭腦清醒的同時滿足所有的要求呢?這是一個問題。
對於web開發人員來說,風險當然比不上作戰的工程師。但是我們面對的也是充滿敵意的環境:效能欠佳的開裝置,糟糕的網路連線,還有前端工程固有的複雜性。Sapper是Svelte app maker的縮寫,是您勇敢而忠誠的盟友。
Sapper的設計目標是輕量級、高效能、易於推廣,同時還要提供足夠的特性來將您的想法轉化為出色的web應用程式。
以下便是Sapper在Svelte中構建Web應用程式時為我們所提供的幫助:
-
Routing
-
SSR
-
自動程式碼分割
-
離線支援(使用Service Workers)
-
高層次專案結構管理
我相信大家都知道,自己管理這些可能很快就會成為一件煩人的事情,不得不讓我們從實際的業務邏輯中分心。
但是說了這麼多,有啥用?那讓我們來看看一個使用Svelte + Sapper的小型伺服器渲染的應用程式。
體驗
下載安裝
Sapper模板提供webpack編譯和rollup編譯兩種方式。
# rollup
npx degit "sveltejs/sapper-template#rollup" my-app
# webpack
npx degit "sveltejs/sapper-template#webpack" my-app
cd my-app
npm install
npm run dev & open http://localhost:3000
複製程式碼
通過這個官方模板,其實我們就可以簡單的瞭解到Sapper的路由處理和SSR,不用探索太深。
專案結構
Sapper比較固執,所以某些要求在開發時必須按照規定的形式執行。
入口
每個Sapper專案都有三個入口檔案和一個src/template.html檔案:
src/client.js
src/server.js
src/service-worker.js
(這個是可選的)
client.js
import * as sapper from '@sapper/app';
sapper.start({
target: document.querySelector('#sapper')
});
複製程式碼
這是客戶端所要呈現的應用程式的入口檔案。它是一個相當簡單的檔案,這裡需要做的就是從@sapper/app匯入Sapper模組,並呼叫start方法。它接受一個物件作為引數,唯一需要的引數就是target。
目標指定應用程式將掛載在哪個DOM節點上。如果熟悉React,可以將其看作是ReactDOM.render。
server.js
我們需要一個伺服器來為使用者提供我們的應用程式,能理解吧?由於這是一個Node.js環境,所以我們有大量的選擇。可以使用Express.js,Koa.js,Polka等等,但是有一些規則還是要遵循的:
-
伺服器必須提供**/static**資料夾的內容。Sapper不在乎你用來做什麼。但是必須要有那個資料夾!
-
採用的伺服器框架必須支援middlewares,並且必須使用從@sapper/server裡面使用sapper.middleware()匯入。
-
採用的伺服器必須監聽process.env.PORT。
只有這三條規定。我們可以實際去看看生成的server.js檔案與操作。
service-worker.js
如果你需要了解一下什麼是service-worker,這篇文章應該不錯。現在,使用Sapper構建功能完整的web應用程式不需要service-worker.js檔案;它只是為了幫助你訪問離線支援、推送通知、後臺同步等功能(所以一般用於移動端)。
我們們可以選擇完全不使用它,也可以使用它來實現更完整的使用者體驗。
template.html
這是應用程式的主要入口頁面,所有元件、樣式參考和指令碼都在這裡按需注入。除了需要通過從HTML連結到CDN來新增模組的少數情況外,它幾乎是固定不動的。
routes
每個Sapper應用程式的重中之重。這是你大部分邏輯和內容的所在。我們將在下一節中進行更深入的研究。
路由
如果啟動了專案,那麼訪問http://localhost:3000就能進入一個簡單的web應用程式,其中包含一個主頁、一個關於頁面和一個部落格頁面。到目前為止,相當簡單。
現在,讓我們嘗試理解Sapper如何能夠協調URL和相應的檔案。在Sapper中,有兩種型別的路由:頁面路由和伺服器路由。
讓我們進一步分析一下。
Page routes
當我們導航到某個頁面例如(/ about)時,Sapper會呈現src/routes資料夾中的about.svelte檔案。 這也意味著該資料夾內的任何.svelte檔案都可以自動“對映”到相同名稱的路由下。 因此,如果src/routes資料夾中有一個名為jump.svelte的檔案,則導航/jumping將能把該檔案的頁面進行渲染。
簡而言之,頁面路由就是src/routes資料夾下的.svelte檔案。這種方法處理的一個非常好的結果是,專案路由的路徑是可預測的,並且很容易理解。我們如果想要一條新路由,只需要在src/routes中建立一個新的.svelte檔案,我們這就成功了!
但如果我們想要一個巢狀的路由即子路由,比如這樣的**/projects/sapper/awesome**。我們只需要做的就是為每個子路由建立一個資料夾。所以,對於上面的例子,你會有一個這樣的資料夾結構:src/route/projects/sapper,然後我們在此資料夾下放置awesome.svelte檔案就可以了。
知道了這一點,我們再回頭看看我們的載入程式,並且導航到‘關於’頁面。按照上面的邏輯,它應該是src/routers下的about.svelte檔案。我們確認後也確實如此。
請注意,index.svelte檔案是一個保留檔案,當您導航到子路由時會呈現該檔案。 例如,在我們模板中,我們有一個/ blogs路由,在其中可以訪問其下的其他子路由,例如/blogs/why-the-name。
但是請注意,當/ blogs本身是資料夾時,在瀏覽器中導航到/blogs時也會呈現一個檔案。 我們如何為這種路線建立檔案?
所以,我們要麼在/ blogs資料夾之外定義一個blog.svelte檔案,要麼我們需要在/ blogs資料夾下放置一個index.svelte檔案,但不能同時放置兩個檔案。 當您直接訪問/ blogs時,將呈現此index.svelte檔案。
接下來我們思索,帶有動態標籤的URL呢? 在我們的示例中,手動建立每個部落格文章並將其儲存為.svelte檔案是不可行的。 我們需要的是一個模板,該模板用於呈現所有部落格文章。
再來看看我們的專案。在src/routes/blogs下面,有一個[slug].svelte的檔案。那是什麼呢?木有錯—它就是一個模板,用於呈現所有的部落格文章,而不管它們是什麼。這意味著/blogs之後的任何標籤都會被這個檔案自動處理,我們可以在頁面掛載時獲取頁面內容,然後將其呈現給瀏覽器。
那麼這是否也就意味著/routes下的任何檔案或資料夾都自動對映到一個URL呢?答案是肯定的,但是也有一個例外。就是如果在檔案或資料夾前面加上下劃線,Sapper不會將其轉換為URL。這可以讓我們很容易地將幫助檔案放在routes資料夾中。
假設我們需要一個helper資料夾來存放所有helper檔案。我們可以使用/routes/_helpers這樣的資料夾,然後放置在/_helpers下的任何檔案都不會被視為路由。完美!
Server routes
在前面的小節中,我們看到了有一個[slug].svelte檔案,可以幫助我們匹配這樣的任何url: /blogs/<any_url>。但是它是如何讓頁面的內容呈現的呢?
我們其實可以從靜態檔案獲取內容,也可以呼叫API來檢索資料。無論哪種方式,您都需要向一個路由(或服務端,前提是如果我們只需要使用API)發出請求來檢索資料。這就是Server routes的主場。
來自官方文件:“伺服器路由是用‘.js’檔案編寫的模組,匯出與HTTP方法對應的函式。”
這只是意味著伺服器路由是您可以呼叫來執行特定操作(如儲存資料、獲取資料、刪除資料等)的服務端。它可以理解為我們應用程式的後端,所以你在一個專案中可以擁有你需要的一切(當然,如果你想的話,你也可以分開它們)。
現在回到我們的啟動專案。如何獲取[slug].svelte中的每一篇部落格文章的內容?開啟這個檔案,你看到的第一個程式碼是這樣的:
<script context="module">
export async function preload({ params, query }) {
// the `slug` parameter is available because
// this file is called [slug].html
const res = await this.fetch(`blog/${params.slug}.json`);
const data = await res.json();
if (res.status === 200) {
return { post: data };
} else {
this.error(res.status, data.message);
}
}
</script>
複製程式碼
我們看到一個簡單的JS函式,該函式發出GET請求並從該請求返回資料。 它以一個物件作為引數,然後在第2行對其進行結構分解以獲取兩個變數:params和query。
params和query包含什麼? 在函式的開頭打個log,然後在瀏覽器中開啟部落格文章,我們會得到如下提示:
{slug: "why-the-name"}slug: "why-the-name"__proto__: Object {}
複製程式碼
嗯。因此,如果我們在第5行開啟“why-the-name”帖子,我們的GET請求將是blog/why-the-name.json。然後在第6行將其轉換為json物件。
在第7行,我們檢查請求是否成功,如果是,則在第8行返回它,否則呼叫一個名為this.error的特殊方法。帶有響應狀態和錯誤訊息的錯誤物件。
很簡單。但是實際的伺服器路由在哪裡呢?它是什麼樣子的呢?檢視src/routes/blog,你會看到一個[slug].json.js檔案——這是我們的伺服器路由。並注意它的命名方式與[slug] .svelte相同?這就是Sapper將伺服器路由對映到頁面路由的方式。如果你需要獲取一個名為example的檔案。Sapper將尋找example.json.js檔案來處理請求。
現在我們來分析分析 [slug].json.js檔案。
import posts from './_posts.js';
const lookup = new Map();
posts.forEach(post => {
lookup.set(post.slug, JSON.stringify(post));
});
export function get(req, res, next) {
// the `slug` parameter is available because
// this file is called [slug].json.js
const { slug } = req.params;
if (lookup.has(slug)) {
res.writeHead(200, {
'Content-Type': 'application/json'
});
res.end(lookup.get(slug));
} else {
res.writeHead(404, {
'Content-Type': 'application/json'
});
res.end(JSON.stringify({
message: `Not found`
}));
}
}
複製程式碼
我們真正感興趣的是從第8行開始。第3-6行只是為要處理的路由準備資料。還記得我們是如何在我們的頁面路由中發出GET請求的嗎?[slug].svelte是處理該請求的伺服器路由。
如果我們熟悉Express.js,那麼我們應該很熟悉它。因為這只是一個簡單的服務。它所做的就是獲取從請求物件傳遞給它的slug,在我們的資料儲存中搜尋它(在本例中是查詢),然後在響應物件中返回它。
如果使用資料庫,第12行可能類似於Posts.find({ where: { slug } })(Sequelize)。
伺服器路由是包含介面的檔案,可以從頁面路由中呼叫它們。 因此,讓我們快速瞭解一下到目前為止所瞭解的知識:
-
頁面路由是src/routes資料夾下的.svelte檔案,用於將內容呈現給瀏覽器。
-
伺服器路由是包含API介面的.js檔案,並按名稱對映到特定的頁面路由。
-
頁面路由可以呼叫伺服器路由中定義的介面,以執行特定的操作,例如獲取資料。
-
Sapper 是經過深思熟慮的!
Server-side rendering(SSR)
伺服器端渲染(SSR)是讓Sapper具有吸引力的重要原因。 如果您不知道SSR是什麼或不知道為什麼需要它,那麼我們來簡單解釋解釋。
預設情況下,Sapper首先在伺服器端呈現所有應用程式,然後在客戶端載入動態元素。這樣一來,我們就可以做到兩全其美,而不用因為分離做出任何妥協。
不過,這裡有一個問題:儘管Sapper在支援第三方模組方面做得近乎完美,但有些模組需要訪問window物件,而且如您所知,您不能從伺服器端訪問window。僅僅匯入這樣的模組將導致您的編譯失敗。
不過,不要煩惱;有一個簡單的解決方法。Sapper允許您動態匯入模組,因此不必在頂層匯入模組。我們需要這麼做:
<script>
import { onMount } from 'svelte';
let MyComponent;
onMount(async () => {
const module = await import('my-non-ssr-component');
MyComponent = module.default;
});
</script>
<svelte:component this={MyComponent} foo="bar"/>
複製程式碼
在第2行,我們匯入onMount函式。onMount函式內建於Svelte中,只有在元件掛載到客戶端時才呼叫它(與React的componentDidMount等效)。
這意味著,當只在onMount執行的時候在其中匯入有問題的模組,而不會在伺服器上呼叫該模組,也不會出現缺少視窗物件的問題。您的程式碼編譯成功,一切恢復正常。
這種方法還有另一個好處:因為我們正在為這個元件使用動態匯入,所以實際上我們初始向客戶端提供的程式碼更少。
結論
我們已經看到和Sapper一起工作是多麼直觀和簡單。路由系統非常容易掌握,即使是初學者,建立一個API來支援你的前端也是相當簡單的,SSR也非常容易實現。
這裡有很多我們沒有涉及的特性,包括預載入、錯誤處理、regex路由等。真正學習的方法就是用它在造東西。
既然我們已經瞭解了Sapper的基礎知識,現在就可以開始使用它了。建立一個小專案,破壞,修復,四處亂搞,然後才能真正感受到Sapper是如何工作的。