Marko.js-ebay.comのフロントエンド

Marko.jsは、Angular、React.js、Vue.js、Svelteほど人気が​​ありません。 Marko.jsは、2015年からオープンソースプロパティになっているebay.comプロジェクトです。実際、ebay.comフロントエンドが構築されるのはこのライブラリ上であり、これにより、開発者にとっての実用的な価値について結論を出すことができます。



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が再作成されます。



どうしてそれが重要ですか



Angular、React.js、Vue.jsの広範な使用で観察された単一ページのアプリケーションの作成の進歩は、前向きな瞬間とともに、クライアント指向のアーキテクチャのいくつかの致命的な間違いを明らかにしました。2013年に、AirbnbのSpike Brehmは、関連するセクションに「軟膏の中のハエ」というタイトルのプログラマティック記事公開しました同時に、すべてのマイナス点がビジネスに打撃を与えます。



  • 最初のページの読み込み時間が長くなります。
  • コンテンツは検索エンジンによってインデックス付けされません。
  • 障害を持つ人々のアクセシビリティの問題。


別の方法として、同形/ユニバーサルアプリケーションの開発のためのフレームワークがついに作成されました:Next.jsとNust.js。そして、もう1つの要素であるパフォーマンスが関係します。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でどのように構築されているかを知るのは興味深いことです)、このような場合にのみ使用するパッケージを使用します-universal-router:



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文字列を解析し、解析された文字列を使用して非同期アクション(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.com2020年

11月22日



All Articles