Marko.js的情况有点类似于Ember.js框架的情况,尽管它实际上是几个高负载站点(例如Linkedin)的前端,但一般的开发人员对此几乎一无所知。就Marko.js而言,可以说他根本不知道。
Marko.js的速度非常快,尤其是在渲染服务器端时。当涉及到服务器渲染时,Marko.js的速度可能与其悠闲的对手保持遥不可及,这是有客观原因的。我们将在建议的材料中讨论它们。
SSR优先框架
Marko.js可以成为经典前端(具有服务器端渲染),单页应用程序(具有客户端渲染)以及同构/通用应用程序(稍后将讨论其示例)的基础。但仍然可以将Marko.js视为SSR优先的库,也就是说,主要专注于服务器渲染。 Marko.js与其他组件框架的区别在于服务器端组件不构建DOM,而是将DOM序列化为字符串,但将其实现为输出流。为了清楚说明其含义,我将列出一个简单的服务器组件:
// Compiled using marko@4.23.9 - DO NOT EDIT
"use strict";
var marko_template = module.exports = require("marko/src/html").t(__filename),
marko_componentType = "/marko-lasso-ex$1.0.0/src/components/notFound/index.marko",
marko_renderer = require("marko/src/runtime/components/renderer");
function render(input, out, __component, component, state) {
var data = input;
out.w("<p>Not found</p>");
}
marko_template._ = marko_renderer(render, {
___implicit: true,
___type: marko_componentType
});
marko_template.meta = {
id: "/marko-lasso-ex$1.0.0/src/components/notFound/index.marko"
};
服务器组件不应与客户端组件相同的想法似乎很自然。在此基础上,最初构建了Marko.js库。我可以假设在其他框架的情况下,这些框架最初是作为面向客户端的,服务器端呈现而构建的,已被粘贴到已经非常复杂的代码库中。这是在体系结构上存在缺陷的决定的地方,在DOM的服务器端重新创建了DOM,以便现有的客户端代码可以不变地重用。
它为什么如此重要
随着Angular,React.js,Vue.js的广泛使用以及积极的时刻,观察到了单页应用程序创建的进展,揭示了面向客户端的体系结构的一些致命错误。早在2013年,Airbnb的Spike Brehm发表了一篇程序化文章,其相关部分的标题为“美中不足”。同时,所有负面因素都影响了企业:
- 第一页的加载时间增加;
- 内容未被搜索引擎索引;
- 残疾人无障碍环境。
作为替代方案,最终创建了用于开发同构/通用应用程序的框架:Next.js和Nust.js。然后另一个因素发挥作用-性能。每个人都知道,在加载复杂的计算时,node.js并不是那么好。而且,当我们在服务器上创建DOM然后开始对其进行序列化时,node.js很快就会消失。是的,我们可以吊起无限数量的node.js副本。但是也许尝试在Marko.js中做同样的事情?
如何开始使用Marko.js
初次接触时,我建议按照文档中的命令使用命令启动
npx @marko/create --template lasso-express
。
结果,我们将为使用配置的Express.js服务器和Lasso链接器(此链接器由ebay.com开发并且最易于集成)进行的项目进一步开发奠定基础。
Marko.js中的组件通常位于扩展名为.marko的文件的/ components目录中。组件代码很直观。如文档所述,如果您知道html,那么您就会知道Marko.js。
该组件在服务器上呈现,然后在客户端上水合。也就是说,在客户端上,我们不会收到静态的html,而是具有状态和事件的完整的客户端组件。
在开发模式下启动项目时,热重装有效。
要构建复杂的应用程序,除了组件库外,我们很可能还需要其他东西,例如,路由,存储,用于创建同构/通用应用程序的框架。遗憾的是,这里的问题与React.js的开发人员在早期面临的问题相同-没有现成的解决方案和众所周知的方法。因此,到目前为止的所有内容都可以称为关于基于Marko.js构建应用程序的对话的简介。
建立同构/通用应用程序
就像我说的那样,关于Marko.js的文章并不多,因此下面的所有内容都是我实验的成果,部分是基于与其他框架的合作。
Marko.js允许您动态(即以编程方式)设置和更改标签或组件的名称-这就是我们将要使用的名称。让我们匹配路线-组件名称。由于Marko.js中没有开箱即用的路由(很有趣的是,它是如何在ebay.com上构建的),我们将使用该软件包,仅用于这种情况-通用路由器:
const axios = require('axios');
const UniversalRouter = require('universal-router');
module.exports = new UniversalRouter([
{ path: '/home', action: (req) => ({ page: 'home' }) },
{
path: '/user-list',
action: async (req) => {
const {data: users} = await axios.get('http://localhost:8080/api/users');
return { page: 'user-list', data: { users } };
}
},
{
path: '/users/:id',
action: async (req) => {
const {data: user} = await axios.get(`http://localhost:8080/api/users/${req.params.id}`);
return { page: 'user', data: { req, user } };
}
},
{ path: '(.*)', action: () => ({ page: 'notFound' }) }
])
通用路由器软件包的功能非常简单。它解析url字符串,并使用已解析的字符串调用异步函数action(req),例如,在其中我们可以访问已解析的字符串参数(req.params.id)。由于action(req)函数是异步调用的,因此我们可以在此处使用API请求初始化数据。
您还记得,在上一节中,一个团队创建了一个项目
npx @marko/create --template lasso-express
。让我们将其作为同构/通用应用程序的基础。为此,我们将稍微更改server.js文件
app.get('/*', async function(req, res) {
const { page, data } = await router.resolve(req.originalUrl);
res.marko(indexTemplate, {
page,
data,
});
});
我们还将更改已加载页面的模板:
<lasso-page/>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Marko | Lasso + Express</title>
<lasso-head/>
<style>
.container{
margin-left: auto;
margin-right: auto;
width: 800px;
}
</style>
</head>
<body>
<sample-header title="Lasso + Express"/>
<div class="container">
<router page=input.page data=input.data/>
</div>
<lasso-body/>
<!--
Page will automatically refresh any time a template is modified
if launched using the browser-refresh Node.js process launcher:
https://github.com/patrick-steele-idem/browser-refresh
-->
<browser-refresh/>
</body>
</html>
<router />组件正是负责加载动态组件的部分,我们从页面属性中的路由器获得名称。
<layout page=input.page>
<${state.component} data=state.data/>
</layout>
import history from '../../history'
import router from '../../router'
class {
onCreate({ page, data }) {
this.state = {
component: require(`../${page}/index.marko`),
data
}
history.listen(this.handle.bind(this))
}
async handle({location}) {
const route = await router.resolve(location);
this.state.data = route.data;
this.state.component = require(`../${route.page}/index.marko`);
}
}
传统上,Marko.js具有this.state,它的更改会导致组件视图的更改,这就是我们正在使用的状态。
您还必须自己实施历史记录工作:
const { createBrowserHistory } = require('history')
const parse = require('url-parse')
const deepEqual = require('deep-equal')
const isNode = new Function('try {return !!process.env;}catch(e){return false;}') //eslint-disable-line
let history
if (!isNode()) {
history = createBrowserHistory()
history.navigate = function (path, state) {
const parsedPath = parse(path)
const location = history.location
if (parsedPath.pathname === location.pathname &&
parsedPath.query === location.search &&
parsedPath.hash === location.hash &&
deepEqual(state, location.state)) {
return
}
const args = Array.from(arguments)
args.splice(0, 2)
return history.push(...[path, state, ...args])
}
} else {
history = {}
history.navigate = function () {}
history.listen = function () {}
}
module.exports = history
最后,应该有一个导航源可以拦截链接单击事件并在页面上调用导航:
import history from '../../history'
<a on-click("handleClick") href=input.href><${input.renderBody}/></a>
class {
handleClick(e) {
e.preventDefault()
history.navigate(this.input.href)
}
}
为了方便学习材料,我在存储库中展示了结果。
apapacy@gmail.com
2020年11月22日