Marko.js - frontend dari ebay.com

Marko.js tidak sepopuler Angular, React.js, Vue.js atau Svelte. Marko.js adalah proyek ebay.com yang telah menjadi properti opensource sejak 2015. Sebenarnya, di pustaka inilah frontend ebay.com dibangun, yang memungkinkan kami menarik kesimpulan tentang nilai praktisnya bagi pengembang.



Situasi dengan Marko.js agak mirip dengan situasi dengan kerangka kerja Ember.js, yang, terlepas dari kenyataan bahwa ia berfungsi sebagai frontend untuk beberapa situs dengan beban tinggi (misalnya, Linkedin), rata-rata pengembang hanya mengetahui sedikit tentangnya. Dalam kasus Marko.js, dapat dikatakan bahwa dia tidak tahu sama sekali.



Marko.js sangat cepat, terutama saat merender di sisi server. Dalam hal rendering sisi server, kecepatan Marko.js kemungkinan besar akan tetap di luar jangkauan untuk rekan-rekannya yang santai, untuk alasan yang bagus. Kami akan membicarakannya dalam materi yang diusulkan.



Kerangka SSR-first



Marko.js dapat menjadi dasar untuk front-end klasik (dengan rendering sisi server), untuk aplikasi satu halaman (dengan rendering sisi klien), dan untuk aplikasi isomorfik / universal (contohnya akan dibahas nanti). Tapi tetap saja, Marko.js dapat dianggap sebagai pustaka yang mengutamakan SSR, yang difokuskan terutama pada rendering server. Yang membedakan Marko.js dari kerangka komponen lainnya adalah komponen sisi server tidak membangun DOM, yang kemudian diserialkan menjadi string, tetapi diimplementasikan sebagai aliran keluaran. Untuk memperjelas tentang apa ini, saya akan memberikan daftar komponen server sederhana:



// 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"
  };


Gagasan bahwa komponen server tidak boleh sama dengan komponen klien tampaknya sangat wajar. Dan atas dasar inilah, perpustakaan Marko.js awalnya dibangun. Saya dapat berasumsi bahwa dalam kasus kerangka kerja lain yang awalnya dibangun sebagai klien-sentris, rendering sisi server direkam ke basis kode yang sudah sangat kompleks. Di sinilah muncul keputusan yang cacat secara arsitektural ini, di mana DOM dibuat ulang di sisi server sehingga kode klien yang ada dapat digunakan kembali tanpa diubah.



Mengapa ini penting



Kemajuan dalam pembuatan aplikasi satu halaman, yang diamati dengan meluasnya penggunaan Angular, React.js, Vue.js, bersama dengan momen-momen positif, mengungkap beberapa kesalahan fatal dari arsitektur berorientasi klien. Kembali pada tahun 2013, Spike Brehm dari Airbnb menerbitkan sebuah artikel terprogram di mana bagian yang relevan berjudul "Seekor lalat di salep." Pada saat yang sama, semua poin negatif mengenai bisnis:



  • waktu pemuatan halaman pertama meningkat;
  • konten tidak diindeks oleh mesin pencari;
  • masalah aksesibilitas bagi penyandang disabilitas.


Sebagai alternatif, framework untuk pengembangan aplikasi isomorfik / universal akhirnya dibuat: Next.js dan Nust.js. Dan kemudian faktor lain ikut bermain - kinerja. Semua orang tahu bahwa node.js tidak begitu bagus saat dimuat dengan penghitungan yang rumit. Dan dalam kasus ketika kita membuat DOM di server dan kemudian mulai menserialisasinya, node.js gagal dengan sangat cepat. Ya, kita dapat mengumpulkan replika node.js dalam jumlah tak terbatas. Tetapi mungkin mencoba melakukan hal yang sama tetapi di Marko.js?



Bagaimana memulai dengan Marko.js



Untuk kenalan pertama, saya sarankan memulai seperti yang dijelaskan dalam dokumentasi dengan perintah npx @marko/create --template lasso-express.



Hasilnya, kami akan mendapatkan dasar untuk pengembangan proyek lebih lanjut dengan server Express.js yang dikonfigurasi dan linker Lasso (linker ini dikembangkan oleh ebay.com dan paling mudah diintegrasikan).



Komponen di Marko.js biasanya terletak di direktori / components di file dengan ekstensi .marko. Kode komponen intuitif. Seperti yang dikatakan dalam dokumentasinya, jika Anda tahu html, maka Anda tahu Marko.js.



Komponen dirender di server dan kemudian terhidrasi di klien. Artinya, pada klien, kami tidak menerima html statis, tetapi komponen klien yang lengkap, dengan status dan peristiwa.



Saat memulai proyek dalam mode pengembangan, hot reload berfungsi.



Untuk membangun aplikasi yang kompleks, kemungkinan besar kita perlu memiliki sesuatu yang lain selain pustaka komponen, misalnya, perutean, penyimpanan, kerangka kerja untuk membuat aplikasi isomorfik / universal. Dan di sini, sayangnya, masalahnya sama dengan yang dihadapi para pengembang React.js di tahun-tahun awal - tidak ada solusi yang siap pakai dan pendekatan terkenal. Oleh karena itu, segala sesuatu yang muncul sampai di sini bisa disebut sebagai pengantar percakapan tentang membangun aplikasi berbasis Marko.js.



Membangun aplikasi isomorfik / universal



Seperti yang saya katakan, tidak banyak artikel tentang Marko.js, jadi semua yang di bawah ini adalah buah dari eksperimen saya, sebagian didasarkan pada bekerja dengan kerangka kerja lain.



Marko.js memungkinkan Anda menyetel dan mengubah nama tag atau komponen secara dinamis (yaitu, secara terprogram) - itulah yang akan kita gunakan. Mari kita cocokkan rute - nama komponen. Karena tidak ada perutean di luar kotak di Marko.js (sangat menarik untuk mengetahui bagaimana ini dibuat di ebay.com), kami akan menggunakan paket, yang hanya untuk kasus seperti itu - 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' }) }
])


Fungsionalitas dari paket universal-router sangat sederhana. Ini mem-parsing string url, dan memanggil fungsi asynchronous action (req) dengan string yang diurai, di dalamnya kita dapat, misalnya, mengakses parameter string yang diurai (req.params.id). Dan karena fungsi action (req) disebut asynchronous, kita dapat menginisialisasi data dengan permintaan API di sini.



Seperti yang Anda ingat, di bagian terakhir sebuah proyek dibuat oleh sebuah tim npx @marko/create --template lasso-express. Mari kita anggap itu sebagai dasar untuk aplikasi isomorfik / universal kita. Untuk melakukan ini, mari ubah sedikit file server.js



app.get('/*', async function(req, res) {
    const { page, data } = await router.resolve(req.originalUrl);
    res.marko(indexTemplate, {
            page,
            data,
        });
});


Kami juga akan mengubah template dari halaman yang dimuat:



<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>


Komponen <router /> persis bagian yang akan bertanggung jawab untuk memuat komponen dinamis, yang namanya kita dapatkan dari router di atribut halaman.



<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`);
  }
}


Secara tradisional, Marko.js memiliki this.state, perubahan yang menyebabkan tampilan komponen berubah, itulah yang kita gunakan.



Anda juga harus mengimplementasikan pekerjaan dengan sejarah sendiri:



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


Terakhir, harus ada sumber navigasi yang mengintersep peristiwa klik link dan panggilan navigasi di halaman:



import history from '../../history'

<a on-click("handleClick") href=input.href><${input.renderBody}/></a>

class {
  handleClick(e) {
    e.preventDefault()
    history.navigate(this.input.href)
  }
}


Demi kenyamanan mempelajari materi, saya mempresentasikan hasilnya di repositori .



apapacy@gmail.com

22 November 2020



All Articles