在下面的文章中,我将展示如何使用服务器端
在 Svelte 中渲染。
大多数时候,您可能会在客户端运行您的 Svelte 代码,
但在某些情况下,您可以从服务器端 Svelte 中受益
渲染。
SEO 在我看来,服务器端渲染的主要用例是
搜索引擎优化。在服务器上进行初始渲染将使您的网站
更多爬虫可访问。爬虫通常支持一些
JavaScript 执行,但不太可能是复杂的 JavaScript 应用程序
被正确索引。
我的经验是,通过 ajax 调用来获取数据的应用程序
索引特别具有挑战性。爬虫可能会成功
呈现应用程序的初始静态部分,但数据将
因为爬虫不会进行必要的 ajax 调用,所以会丢失。
在服务器上进行初始渲染允许爬虫下载
应用程序作为完全构造的 html。无需执行
客户端上的 JavaScript 组成初始视图,因为视图
已经在服务器上构建了。
服务器端与客户端 从技术上讲,您可以构建一个 Svelte
应用程序没有任何客户端组件,但它会
完全静止。基本上,应用程序的行为很像
旧的服务器端 PHP 应用程序。非常适合爬虫,但真正的用户
通常期望获得更丰富的用户体验。
这是服务器端与客户端相遇的地方。
一旦服务器生成的 html 在浏览器中完全呈现,我们
可以启动应用程序的客户端副本。这
客户端版本选择服务器端应用程序离开的地方
关闭。
两个版本的应用程序可能使用相同的 Svelte 组件,
但重要的是要了解它们独立执行
彼此。服务器端版本不知道
客户端版本,反之亦然。
了解没有默认状态也很重要
客户端和服务器之间的共享(例如数据属性)。
Article Carousel 我决定使用服务器端 Svelte 来构建一个
我博客登陆页面的文章轮播。这个想法是使用一个
Svelte 组件循环浏览我的四篇文章中的文章
类别。
轮播应该在页面加载时立即加载,所以我决定
在服务器上渲染初始视图。页面加载后,我
启动 Svelte 组件的客户端副本以
动态循环浏览文章。
我不擅长 CSS 或设计技能,所以不要指望漂亮的 UI,
但这是我把所有东西都连接起来的方法。
我首先创建一个用于渲染的 Article 组件
轮播中的文章。该组件可能应该被拆分
分为两个组件,但出于此目的将其保留为一个组件
博文。
<div class="row">
<span class="slide-show-card col-sm-3">
<h4>Angular</h4>
<h5><a class="slide-show-link" href="{{angularUrl}}">{{angularTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{angular.viewCount}} times</div>
<div>{{angularIntro}}</div>
</span>
<span class="slide-show-card col-sm-3">
<h4>JavaScript</h4>
<h5><a class="slide-show-link" href="{{javascriptUrl}}">{{javascriptTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{javascript.viewCount}} times</div>
<div>{{javascriptIntro}}</div>
</span>
<span class="slide-show-card col-sm-3">
<h4>Svelte</h4>
<h5><a class="slide-show-link" href="{{svelteUrl}}">{{svelteTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{svelte.viewCount}} times</div>
<div>{{svelteIntro}}</div>
</span>
<span class="slide-show-card col-sm-3">
<h4>React</h4>
<h5><a class="slide-show-link" href="{{reactUrl}}">{{reactTitle}}</a></h5>
<div class="label label-success slide-show-count">Viewed {{react.viewCount}} times</div>
<div>{{reactIntro}}</div>
</span>
</div>
<script>
class ArticleService {
constructor() {
this.articles = [];
this.index = {
'angular': -1,
'javascript': -1,
'svelte': -1,
'react': -1
};
}
getArticles() {
return fetch('/full-article-list-json')
.then((data) => {
return data.json();
})
.then((articles) => {
this.articles = articles;
return articles;
});
}
getNextArticle(key) {
this.index[key] = (this.index[key] + 1) % this.articles[key].length;
let a = this.articles[key][this.index[key]];
return a;
}
}
let articleService = new ArticleService();
export default {
onrender() {
let update = () => {
this.set({angular: articleService.getNextArticle('angular')});
this.set({javascript: articleService.getNextArticle('javascript')});
this.set({svelte: articleService.getNextArticle('svelte')});
this.set({react: articleService.getNextArticle('react')});
};
articleService.getArticles()
.then((articles) => {
update();
if(typeof global === 'undefined') {
document.querySelector('#articles').innerHTML = '';
document.querySelector('#articles-client-side').style.display =
"block";
}
})
.then(() => {
setInterval(() => {
articleService.getArticles()
.then((articles) => {
update();
});
}, 5000);
});
},
data () {
return {}
},
computed: {
angularTitle: angular => angular.title || '',
angularIntro: angular => angular.intro || '',
angularUrl: angular => `viewarticle/${angular.friendlyUrl}`,
javascriptTitle: javascript => javascript.title || '',
javascriptIntro: javascript => javascript.intro || '',
javascriptUrl: javascript => `viewarticle/${javascript.friendlyUrl}`,
svelteTitle: svelte => svelte.title || '',
svelteIntro: svelte => svelte.intro || '',
svelteUrl: svelte => `viewarticle/${svelte.friendlyUrl}`,
reactTitle: react => react.title || '',
reactIntro: react => react.intro || '',
reactUrl: react => `viewarticle/${react.friendlyUrl}`,
}
}
</script>
服务器我们来看看服务器代码。
我们要做的第一件事是通过要求激活编译器
苗条/ssr/注册
require( 'svelte/ssr/register' );
接下来我们必须要求组件 html 文件来获取
组件。
然后我们调用 render 方法并将初始数据对象传递给它。这
数据对象是标准的 Svelte 数据对象。
app.get('/', function(req, res) {
var component = require('./app/article-show/Articles.html');
var articles = component.render({
angular: articles.angular,
svelte: articles.svelte,
react: articles.react,
javascript: articles.javascript
});
res.render('index.html', {articles: articles});
});
调用 render 后,我们得到一个完全渲染的组件。我们现在
必须将其传递给节点视图引擎。
在我的例子中,我使用的是带 Mustache 的 Express,所以我可以通过
组件作为渲染方法的对象。然后在我的 index.html
页面我使用带有三重花括号的 Mustache 视图语法来呈现
页面上的组件就像这样。
{{{articles}}} 客户端 我们目前所拥有的足以渲染
我的组件的初始视图,但它不支持通过新组件循环
每隔几秒就有一篇文章。
为此,我们必须启动客户端版本的
文章组件。
客户端版本作为标准 Svelte 客户端加载
组件。
import articles from './articles';
var articlesSection = new articles({
target: document.querySelector( 'main' ),
data: {angular: {}, javascript: {}, svelte: {}, react: {}}
});
一旦客户端版本被激活,我们将拥有两个组件
在 DOM 中。一旦客户端版本准备好接管我
清除服务器端版本。
可能有更优雅的方式来做到这一点,但我只是清除
服务器生成 DOM 元素并在客户端翻转样式
版本。
导航除了文章轮播我还建立了我的主要
导航作为 Svelte 服务器端组件。导航是一个纯服务器
没有客户端对应的侧组件。
导航大部分是静态的,但它支持动态样式
活动的导航项目。这里是导航组件代码:
<div class="nav col-md-2 hidden-sm hidden-xs">
<a class="navLink" href="/"><div class="navBox {{home}}">Home</div></a>
<a class="navLink" href="/most-popular"><div class="navBox {{mostpopular}}">Most Popular</div></a>
<a class="navLink" href="/most-recent"><div class="navBox {{mostrecent}}">Most Recent</div></a>
<a class="navLink" href="/articleList/angular"><div class="navBox {{angular}}">Angular</div></a>
<a class="navLink" href="/articleList/react"><div class="navBox {{react}}">React</div></a>
<a class="navLink" href="/articleList/aurelia"><div class="navBox {{aurelia}}">Aurelia</div></a>
<a class="navLink" href="/articleList/javascript"><div class="navBox {{javascript}}">JavaScript</div></a>
<a class="navLink" href="/articleList/nodejs"><div class="navBox {{nodejs}}">NodeJS</div></a>
<a class="navLink" href="/articleList/vue"><div class="navBox {{vue}}">Vue</div></a>
<a class="navLink" href="/articleList/svelte"><div class="navBox {{svelte}}">Svelte</div></a>
<a class="navLink" href="/articleList/mongodb"><div class="navBox {{mongodb}}">MongoDB</div></a>
<a class="navLink" href="/articleList/unittesting"><div class="navBox {{unittesting}}">UnitTesting</div></a>
<a class="navLink" href="/articleList/dotnet"><div class="navBox {{dotnet}}">.Net</div></a>
<a class="navLink" href="/questions"><div class="navBox {{questions}}">Q&A</div></a>
<a class="navLink" href="/full-article-list"><div class="navBox {{all}}">All</div></a>
</div>
<script>
export default {
data () {
return {}
},
}
</script>
由于导航是如此静态,没有理由重新生成
每个请求的服务器端 html。作为优化,我决定
缓存导航的不同变化。唯一的区别
不同版本的导航之间是“活动”导航
items 样式应用于不同的元素。
这里有一些基本的缓存逻辑,确保我们只生成
每个导航版本一次。
function getNavComponent(key) {
key = key || 'home';
key = key.replace('-', '');
if(!navCache[key]) {
navCache[key] = createNavComponent(key);
}
return navCache[key];
}
function createNavComponent(key) {
var nav = require('./app/article-show/Navigation.html');
var data = {};
data[key] = 'activeNav';
return nav.render(data);
}
Demo 如果你想测试我的服务器端与客户端视图,你
可以找到here。
我还在 Google 网站管理员工具中加载了我的网站,以比较
爬虫的组件视图到用户的视图
从屏幕截图中可以看出,我的组件看起来不错
添加服务器端渲染后的爬虫。
左侧是爬虫视图,右侧是用户视图。