如何渲染 Svelte 服务器端?

Posted

技术标签:

【中文标题】如何渲染 Svelte 服务器端?【英文标题】:How to render Svelte Server-side? 【发布时间】:2020-11-13 12:27:24 【问题描述】:

我能否以某种方式使用 Svelte 生成“初始”html 文件?

我正在使用 Django、Webpack 和 Tailwindcss。我想在我的前端使用 Svelte,但我不想放弃简单使用服务器端渲染(Django 模板)带来的速度。如果我最初展示的是一个引入 bundle.js 的引导 HTML 页面,并且 Svelte 在客户端构建 DOM,那么浏览器只有在加载 JS 文件后才开始下载图像。

与最初渲染的 HTML 已经包含图像链接相比,浏览器开始将它们与 JS 一起下载,从而更快地感知页面加载。

我不想使用 Sapper 作为我的应用服务器,我想继续使用 Django。

【问题讨论】:

您看过 Svelte 的服务器端渲染 (s-s-r) 功能吗?看来答案是肯定的。 【参考方案1】:

挑战在于在 Django 应用和 Svelte 组件之间共享状态(道具)。

从组件中获取 HTML 代码:

require('svelte/register')
const MyComponent = require('./MyComponent.svelte').default

const  html  = MyComponent.render( ...props... )

如果组件没有 props,您可以编译和缓存 HTML 模板(甚至可能在运行时之前)。

如果您想动态发送道具,例如基于数据库中的数据,那么您需要在运行时这样做。这意味着在服务器端执行 JS。如果缓存结果,性能不会很差。

如果你不能缓存,那么使用 Django 来提高性能将被否定,因为无论如何你都会执行 Svelte,所以不妨使用 Svelte 来完成整个服务器端工作,然后使用 Django 作为后端服务器。

【讨论】:

【参考方案2】:

我认为,这没有多大意义。您可以为每个页面定义一个模板,该模板有一个静态的预渲染部分,然后是一个由纤巧应用控制的动态内容区域。

但是所有导航都会加载一个新模板,它可能不再是单边应用程序了。而且您可能会为不同的页面模板编写不同的苗条应用程序(rollup 可以做到)。

我认为,这样做会损失很多性能,在这种情况下,我宁愿选择 angular 而不是 svelte(或 react 或 vue)。

【讨论】:

这不能回答问题。【参考方案3】:

根据this blog post,您执行以下指令:

在下面的文章中,我将展示如何使用服务器端 在 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>

服务器我们来看看服务器代码。

我们要做的第一件事是通过要求激活编译器 苗条/s-s-r/注册

require( 'svelte/s-s-r/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 网站管理员工具中加载了我的网站,以比较 爬虫的组件视图到用户的视图

从屏幕截图中可以看出,我的组件看起来不错 添加服务器端渲染后的爬虫。

左侧是爬虫视图,右侧是用户视图。

【讨论】:

你为什么只是复制粘贴整篇文章?您能否从中获取基本信息并回答问题?

以上是关于如何渲染 Svelte 服务器端?的主要内容,如果未能解决你的问题,请参考以下文章

如何在服务器端做出反应来渲染图像?

如何在 React/redux 中进行服务器端渲染?

react + redux + saga +服务器端渲染+如何停止服务器端渲染页面的额外ajax调用?

如何使用角度通用在服务器端渲染中加载 json

前端如何高效的与后端协作开发

前端如何高效的与后端协作开发