为什么我们要用网页端组件去构建服务器?该怎么做?

本文讲的是为什么我们要用网页端组件去构建服务器?该怎么做?,

Web components(网页组件)用在服务器端渲染早已被大家所了解,在本文中,我想谈及的是:你还可以用 web components 构建服务器端应用。

先来回顾一下,web components 是一组 新提出的标准,提供了模块化打包 UI 组件的能力,这些组件具有可重用、声明式的特点,因此具有方便分享、容易组合到应用的优势。现在 web components 已经开始应用到前端开发当中了,但服务器端呢?Polymer 项目 给了我们启发,web components 不仅对于 UI 组件很有用,而且还可以用在原始功能中。接下来我们会看看 web components 如何应用到服务器端,并分析其优势。

  • 声明式
  • 模块化
  • 通用化
  • 可共享
  • 可调试
  • 更平缓的学习曲线
  • 客户端结构

声明式

首先,web components 让你得到了声明式的服务器。以下是一个使用 Express Web Components 编写 Express.js 应用程序的简单示例:

<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/express-web-components/express-app.html">
<link rel="import" href="bower_components/express-web-components/express-middleware.html">
<link rel="import" href="bower_components/express-web-components/express-router.html">

<dom-module id="example-app">
    <template>
        <express-app port="5000">
            <express-middleware method="get" path="/" callback="[[indexHandler]]"></express-middleware>
            <express-middleware callback="[[notFound]]"></express-middleware>
        </express-app>
    </template>

    <script>
        class ExampleAppComponent {
            beforeRegister() {
                this.is = 'example-app';
            }

            ready() {
                this.indexHandler = (req, res) => {
                    res.send('Hello World!');
                };

                this.notFound = (req, res) => {
                    res.status(404);
                    res.send('not found');
                };
            }
        }

        Polymer(ExampleAppComponent);
    </script>
</dom-module>

现在你可以使用 HTML 语法来声明路由了。比起纯 JavaScript 语法,现在的路由可以体现出视觉层次感,看起来更加形象和易于理解。拿上面的例子来说,所有和 Express 框架有关的 endpoints(路由) / middleware(中间件) 都嵌套在 <express-app> 元素,连接 app 的中间件按顺序放在 <express-middleware> 元素中。而路由也可以很容易地嵌套,<express-router> 中包含的每个中间件都会连接到 router,你还可以把 <express-route> 放在 <express-router> 元素中。

模块化

使用 Express 和 Node.js 已经让我们实现了模块化,但我觉得模块化 web components 更加简单。以下 这个例子 把模块化的自定义元素和 Express Web Components 结合起来使用:

<!--index.html-->

<!DOCTYPE html>

<html>
    <head>
        <script src="../../node_modules/scram-engine/filesystem-config.js"></script>
        <link rel="import" href="components/app/app.component.html">
    </head>

    <body>
        <na-app></na-app>
    </body>

</html>

index.html 是入口文件,实际上我们需要关心的地方只有一个,就是 <na-app></na-app> :

<!--components/app/app.component.html-->

<link rel="import" href="../../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-app.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-middleware.html">
<link rel="import" href="../api/api.component.html">

<dom-module id="na-app">
    <template>
        <express-app port="[[port]]" callback="[[appListen]]">
            <express-middleware callback="[[morganMW]]"></express-middleware>
            <express-middleware callback="[[bodyParserURLMW]]"></express-middleware>
            <express-middleware callback="[[bodyParserJSONMW]]"></express-middleware>
            <na-api></na-api>
        </express-app>
    </template>

    <script>
        class AppComponent {
            beforeRegister() {
                this.is = 'na-app';
            }

            ready() {
                const bodyParser = require('body-parser');
                const morgan = require('morgan');

                this.morganMW = morgan('dev'); // 把请求记录在控制台

                // 配置 body parser
                this.bodyParserURLMW = bodyParser.urlencoded({ extended: true });
                this.bodyParserJSONMW = bodyParser.json();

                this.port = process.env.PORT || 8080; // 设置端口

                const mongoose = require('mongoose');
                mongoose.connect('mongodb://@localhost:27017/test'); // 连接数据库

                this.appListen = () => {
                    console.log(`Magic happens on port ${this.port}`);
                };
            }
        }

        Polymer(AppComponent);
    </script>
</dom-module>

我们启动 Express 应用,监听 port 8080 或者 process.env.PORT 端口,然后定义了三个中间件和一个自定义元素。希望你直觉上就能理解那三个中间件会在<na-api></na-api> 之前运行的工作原理:

<!--components/api/api.component.html-->

<link rel="import" href="../../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-middleware.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-router.html">
<link rel="import" href="../bears/bears.component.html">
<link rel="import" href="../bears-id/bears-id.component.html">

<dom-module id="na-api">
    <template>
        <express-router path="/api">
            <express-middleware callback="[[allMW]]"></express-middleware>
            <express-middleware method="get" path="/" callback="[[indexHandler]]"></express-middleware>
            <na-bears></na-bears>
            <na-bears-id></na-bears-id>
        </express-router>
    </template>

    <script>
        class APIComponent {
            beforeRegister() {
                this.is = 'na-api';
            }

            ready() {
                // 这个中间件应用在 /api 前缀开头的所有请求
                this.allMW = (req, res, next) => {
                    // 输出日志
                    console.log('Something is happening.');
                    next();
                };

                // 测试路由,目的是确保功能正常 (通过 GET http://localhost:8080/api 访问)
                this.indexHandler = (req, res) => {
                    res.json({ message: 'hooray! welcome to our api!' });
                };
            }
        }

        Polymer(APIComponent);
    </script>
</dom-module>

所有 <na-api></na-api> 的内容都包裹在 <express-router></express-router> 当中。组件里的所有中间件都在访问 /api 时生效。接下来再看看 <na-bears></na-bears> 和 <na-bears-id></na-bears-id>

<!--components/bears/bears.component.html-->

<link rel="import" href="../../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-middleware.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-route.html">

<dom-module id="na-bears">
    <template>
        <express-route path="/bears">
            <express-middleware method="post" callback="[[createHandler]]"></express-middleware>
            <express-middleware method="get" callback="[[getAllHandler]]"></express-middleware>
        </express-route>
    </template>

    <script>
        class BearsComponent {
            beforeRegister() {
                this.is = 'na-bears';
            }

            ready() {
                var Bear = require('./models/bear');

                // 创建一只熊 (调用 POST http://localhost:8080/bears)
                this.createHandler = (req, res) => {
                    var bear = new Bear();      // create a new instance of the Bear model
                    bear.name = req.body.name;  // set the bears name (comes from the request)

                    bear.save(function(err) {
                        if (err)
                            res.send(err);
                        res.json({ message: 'Bear created!' });
                    });
                };

                // 获取所有熊 (调用 GET http://localhost:8080/api/bears)
                this.getAllHandler = (req, res) => {
                    Bear.find(function(err, bears) {
                        if (err)
                            res.send(err);
                        res.json(bears);
                    });
                };
            }
        }

        Polymer(BearsComponent);
    </script>
</dom-module>
<!--components/bears-id/bears-id.component.html-->

<link rel="import" href="../../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-middleware.html">
<link rel="import" href="../../../../bower_components/express-web-components/express-route.html">

<dom-module id="na-bears-id">
    <template>
        <express-route path="/bears/:bear_id">
            <express-middleware method="get" callback="[[getHandler]]"></express-middleware>
            <express-middleware method="put" callback="[[updateHandler]]"></express-middleware>
            <express-middleware method="delete" callback="[[deleteHandler]]"></express-middleware>
        </express-route>
    </template>

    <script>
        class BearsIdComponent {
            beforeRegister() {
                this.is = 'na-bears-id';
            }

            ready() {
                var Bear = require('./models/bear');

                // 根据 id 获取某只熊
                this.getHandler = (req, res) => {
                    console.log(req.params);
                    Bear.findById(req.params.bear_id, function(err, bear) {
                        if (err)
                            res.send(err);
                        res.json(bear);
                    });
                };

                // 根据 id 修改某只熊
                this.updateHandler = (req, res) => {
                    Bear.findById(req.params.bear_id, function(err, bear) {
                        if (err)
                            res.send(err);
                        bear.name = req.body.name;
                        bear.save(function(err) {
                            if (err)
                                res.send(err);
                            res.json({ message: 'Bear updated!' });
                        });
                    });
                };

                // 根据 id 删除某只熊
                this.deleteHandler = (req, res) => {
                    Bear.remove({
                        _id: req.params.bear_id
                    }, function(err, bear) {
                        if (err)
                            res.send(err);
                        res.json({ message: 'Successfully deleted' });
                    });
                };
            }
        }

        Polymer(BearsIdComponent);
    </script>
</dom-module>

如你所见,所有路由都被分离到各自的组件中,并且要包含在 app 中也很容易。在 index.html 文件中的 import 引入方法非常浅显易懂,

通用性

我喜欢 JavaScript 的原因之一,就是可以在客户端和服务器端共享代码。虽然现在某种程度上可以说这是可行的,但实际上,由于某些 API 的缺失,依然有一部分客户端的库不能在服务器端工作,反之亦然。从根本上说,Node.js 和浏览器依然是提供不同 API 的两套环境。那有什么办法可以结合呢?我们想到了 Electron,Electron 把 Node.js 和 Chromium 项目结合成为一个单独的运行环境,使得客户端代码和服务端代码结合运行成为了可能。

Scram.js 这个小项目可以帮你轻松运行 Electron,使得运行服务端 web components 和其它 Node.js 应用一样容易。

我已经做出了一些小应用,并放上了生产环境。如果你感兴趣,可以看看 Dokku Example 。

现在,我来告诉你在开发服务端 Web components 过程中一件有意思的事情。我使用一个 客户端的 JavaScript 库 进行某些特定的 API 请求。然而,假如请求放在客户端,就必须把我们数据库的凭证泄露给客户端。为了保证凭证安全,我们需要把请求放在服务端进行。假如要在 Node.js 运行这个库,需要对代码进行大幅重构,幸亏我们用上了 Electron 和 Scram.js,我只需要导入这个库,无需任何代码改动,就顺利在服务端运行起来了!

我只需要导入这个库,无需任何代码改动,就顺利在服务端运行起来了!

另外,我曾经使用 JavaScript 构建一些移动端应用。我们使用 localForage 作为客户端数据库。这个应用是基于分布式数据库设计的,可以在没有中心服务器的情况下进行互相通信。我希望可以在 Node.js 环境下使用 localForage,使得模型可以重用,以及不需要太多修改就能把功能跑起来。过去我们不能做到这点,但现在我们可以做到了。

Electron 和 Scram.js 提供了 LocalStorage, Web SQL 和 IndexedDB,使得 localForage 成为了可能。我们就这样搭建起了一个简单的服务器端数据库!

虽然我不确定怎样测量其性能,但至少这个方法是可行的。

而且,现在你可以在服务端使用像 iron-ajax 和我的 redux-store-element 组件了,使用方法和客户端一样。我希望这么做可以让客户端的范式可重用,并减少从客户端到服务端之间因环境切换而产生的不可避免的差异性。

可共享

这点完全得益于 web components,因为 web components 的其中一个主要目标就是使得组件易于共享,实现跨浏览器通用,并停止在同一个问题上因为框架或者库的改变而不断重复实现。共享之所以变得可能,是因为 web components 基于现有的或者提出的标准,所有主流浏览器的厂商都会想办法去实现。

这意味着 web components 不依赖于任何框架或者库,就可以在任何 web 平台上通用。

我希望有更多人能参与到创建服务器端 web components 的创建当中,并把各种功能打包成组件,就像前端组件那样。我从 Express components 开始了这项工作,但我还期待看到 Koa, Hapi.js, Socket.io, MongoDB 等组件的出现。

可调试

Scram.js 有一个 -d 选项,让你可以在调试时打开 Electron 窗口。现在你可以使用 Chrome 开发者工具的所有功能来帮助你调试服务器了。断点、控制台日志、网络信息等都可以在里面看到。Node.js 的服务端调试似乎总是我的第二选择,但现在它的确已经集成到了平台中:

更平缓的学习曲线

服务端 web components 对降低后端编程学习难度有帮助。要知道有很多 web 设计师、交互设计和其他一些只懂 HTML 和 CSS 的人希望学习服务端开发,但现有的服务器端代码对于他们而言很难理解。然而,如果使用他们熟悉的 HTML 来编写,特别是用上语义化的自定义元素,他们就能更容易地上手服务器端编程了。至少我们可以降低他们的学习曲线吧。

客户端架构

客户端和服务端 app 的架构正变得越来越像了。每个 app 以 index.html 文件开始,然后引入相关组件。这只是一种新的统一前后端的方法。在过去,我觉得想要找到服务器端应用的入口多少有点困难,如果后端能像前端应用一样,以 index.html 作为标准的入口,不是挺好的吗?

以下是使用 web components 构建的客户端应用的一般结构:

app/
----components/
--------app/
------------app.component.html
------------app.component.js
--------blog-post/
------------blog-post.component.html
------------blog-post.component.js
----models/
----services/
----index.html

以下是使用 web components 构建的服务端应用的一般结构:

app/
----components/
--------app/
------------app.component.html
------------app.component.js
--------api/
------------api.component.html
------------api.component.js
----models/
----services/
----index.html

这两种结构应该都可以很好地工作,现在我们成功减少了从客户端到服务端切换的上下文数量,反之亦然。

可能存在的问题

Electron 在服务器生产环境中的性能和稳定性,是最有可能导致应用崩溃的原因。话虽这么说,我并不觉得性能在将来是一个大问题,因为 Electron 只是通过一个渲染进程运行 Node.js 代码,我猜想和原生 Node.js 的运行状况差不多。最大问题是,Chromium 的运行时能否足够稳定,坚持运行足够长时间(而不发生内存泄露)。

另一个潜在问题是冗余性,相比原生 JavaScript 逻辑,使用服务端 web components 完成相同任务会花费更多时间,因为标记语言需要解析。话虽这么说,我依然希望付出冗余的代价,能换来更容易理解的代码。

性能测试

基于好奇心,我进行了一系列基础测试,对比同一个 应用 在原生 Node.js + Express 框架,和 Electron + Scram.js 的 Express Web Components 运行性能对比。下面的图标展示出对于主路由使用node-ab 库进行压力测试的结果。以下是测试用到的一些参数:

  • 在本地机器上运行
  • 每秒递增 100 次 GET 请求
  • 运行直到有 1% 的请求返回不成功
  • 对于 Node.js app 和 Electron/Scram.js app 版本,分别运行 10 次测试
  • Node.js app
    • 使用 Node.js v6.0.0 版本
    • 使用 Express v4.10.1 版本
  • Electron/Scram.js app
    • 使用 Scram.js v0.2.2 版本

      • 默认设置(从本地服务器加载起始 html 文件)
      • 调试窗口关闭
    • 使用 Express v4.10.1 版本
    • 使用 electron-prebuilt v1.2.1 版本
  • 运行库: https://github.com/doubaokun/node-ab
  • 运行命令: nab http://localhost:3000 --increase 100 --verbose

以下是结果 (QPS: Queries Per Second 每秒查询数):

出乎意料,Electron/Scram.js 比 Node.js 性能更佳。我们对这个结果持保留意见,但起码还是能反映出使用 Electron 作为服务器的性能不会比 Node.js 差很远,至少在短期内处理原始请求的效果是如此。还记得我之前说过“我并不觉得性能在将来是一个大问题”吗?结果证实了我的描述。

总结

Web components 很好很强大,给 Web 平台带来了标准化、声明式的组件模型。Web components 不仅能给客户端带来便利,而且在服务端也获益良多。客户端和服务端之间的差距正在缩小,我相信服务器端 web components 是正确方向上的一大迈进。因此,一起来使用它们构建我们的应用吧!






原文发布时间为:2016年08月02日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-08-03 13:24:19

为什么我们要用网页端组件去构建服务器?该怎么做?的相关文章

spring mvc-想自己做一个网站,包括网页端和手机端,第一次做在技术选型上寻求帮助

问题描述 想自己做一个网站,包括网页端和手机端,第一次做在技术选型上寻求帮助 打算用这样的技术选型: 1.CSS框架:jQuery UI Bootstrap[没接触过,在CSDN上看到就想着融入用一下,以前用Jquery easyUi,风格感觉不美观,调动也好像不容易,个人CSS比较烂] 1.HTML5+JSP 2.JS:jquery 4.框架:spring MVC 5.数据库:mybatis 欢迎各位大神提供选型意见,最好能告诉我之所以那样选择的原因,想自己做一个水果店的网站,可能到时候会涵盖

文件分享-安卓和网页端的文件共享

问题描述 安卓和网页端的文件共享 我想要做一个类似于电子笔记一样的安卓应用,安卓端不同用户的笔记可以同步到云端并且也能从云端下载到手机,然后在WEB端(HTML)同样需要能够下载笔记以及上传笔记,请问应该选用哪种AZURE开发方案?谢谢 解决方案 可以使用azure website + blob存储 解决方案二: 您好, 根据您的描述,在我看来可能和您的应用架构有些关系.通常情况下,您可以需要sql server/sql Azure/Azure Table storage 去存储您的用于信息.

django如何将mysql中表的内容通过models.py在网页端显示

问题描述 django如何将mysql中表的内容通过models.py在网页端显示 各位朋友们好,mysql数据库中有1张表student包含了nameage两个字段,共1000条数据,现在我想通过django在网页端展示这些数据.看了网上的教程,我在models.py中添加了:from django.db import modelsimport MySQLdbimport mysite.settingsclass student(models.Model): name = models.Cha

码云推荐 | WEB 网页端在线流程设计器

这是一个用来在WEB网页端设计流程图的UI组件,在线流程设计器,基于Jquery开发.可用来设计各种流程图.逻辑流图,数据流图,或者是设计某个系统中需要走流程的功能应用.良好的用户体验使得操作界面很容易上手,技术开发人员和用户都可使用. 并且兼容主流浏览器(ie8--ie edge,chrome,firefox). 本文来自开源中国社区 [http://www.oschina.net]

Java网页浏览器组件介绍

简介:使用 Java 开发客户端应用有时会需要使用到浏览器组件,本文将介绍在 Java 用户界面中使 用浏览器的四种方法,并且比较它们各自的优点与不足,便于 Java 开发者在实际开发过程中选择. 前言 在使用 Java 开发客户端程序时,有时会需要在界面中使用网页浏览器组件,用来显示一段 HTML 或 者一个特定的网址.本文将介绍在界面中使用浏览器组件的四种方法,给出示例的代码,并且分析每种方 法的优点与不足,便于 Java 开发者在实际开发过程中根据自己的需要来选择. JDK 中的实现 -

电脑网页端QQ空间视频如何关闭自动播放

  电脑网页端QQ空间视频如何关闭自动播放          首先用浏览器进入网页端qq空间,在空间首页顶端工具栏点击设置小齿轮 在左侧设置的菜单栏中选择应用设置 进入应用设置界面后选择视频设置后的编辑设置 好啦!此时我们选择[看到的视频不会自动播放]这一栏,最后提交,这样空间的视频就不会自动播放啦!

中消协表示关注“携程搭售”,APP与网页端现区分搭售与否

十一过后,韩雪发博控诉携程,称自己作为携程老用户,却依然被套路."曾经多次发现,并手动取消隐藏在订票信息下的'预选保险框'."而此次发声的导火索是,韩雪订机票时再次被默认订付了38元的酒店优惠券.此博一出,再加上媒体的跟进,再次引发携程的信任危机. 据法制晚报报道,中国消费者协会新闻与公共事务部一位负责人于昨日接受采访时表示:"中消协已经关注到此事,对于进展,中消协也将密切关注." 而据雷锋网(公众号:雷锋网)了解,早在今年五月,中国消费者协会针对媒体报道携程存在&

服务器-我要把网页端视频上传到腾讯云点播视频那里,有什么办法呢?

问题描述 我要把网页端视频上传到腾讯云点播视频那里,有什么办法呢? 我要把网页端视频上传到腾讯云点播视频那里,我现在是让用户在管理网页上传视频到自己服务器然后再通过云接口传到腾讯云点播平台,但这样好像始终要经过我自己的服务器,就要通过我自己的网页这样子上传的话有什么办法呢? 解决方案 http://www.zhihu.com/question/39649957/answer/82476198 解决方案二: 有木有做过类似的分享下呗

优化-js 手机网页端摇一摇记录摇动次数

问题描述 js 手机网页端摇一摇记录摇动次数 在网页上设置监听判断手机是否摇动,为什么有的时候摇动手机能够监测到摇动,有的时候就不能监测到摇动,感觉手机网页卡掉了一样,代码还是一样的代码啊,请问这应该怎么优化啊 <!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <sty