Architektura pluginů s Meteorem

Psaní balíčků pro Meteor je snadné a přímočaré. Pokud však chcete svým uživatelům umožnit, aby si sami rozšiřovali svou aplikaci, musíte obvykle implementovat nějakou architekturu pluginů.

Tímto způsobem můžete výrazně řídit, jaké funkce mohou uživatelé přidat v rámci vámi definovaných limitů.

V tomto tutoriálu se chceme zaměřit na potenciální přístup k načítání zásuvných modulů z balíčků bez jejich přímého importu, ale pomocí dynamického mechanismu:

  • neměla by být vyžadována žádná ruční konfigurace nastavení
  • neměl by být vyžadován žádný ruční import pluginu
  • přidán balíček pluginu -> dostupný plugin
  • balíček pluginu odstraněn -> plugin není dostupný

Kromě toho by mělo existovat velmi důležité omezení:

  • do počátečního klientského balíku by neměl být přidán žádný plugin, pokud není načten pomocí plugin-loader (představte si 100 pluginů načtených při spuštění aplikace -> super pomalé)

Minimální příklad projektu

Pro tento tutoriál vytvoříme minimální příklad projektu. Zde používám výchozí hodnoty, včetně Blaze (výchozí frontend Meteoru). To by vám však nemělo bránit ve výběru vašeho oblíbeného rozhraní, protože navrhovaná architektura pluginu bude (a měla by!) na něm fungovat nezávisle.

Přípravy – Přehled architektury

Náš příklad se bude skládat ze tří hlavních entit:

  • Meteorický projekt "plugin-example"
  • Balík "plugin-loader"
  • Balík "hello-plugin"

Jejich vztah je poměrně jednoduchý:Pluginy použijí plugin-loader k „registraci“, zatímco projekt Meteor používá plugin-loader k načtení pluginů prostřednictvím dynamického importu. Tedy plugin-loader balíček musí být balíček sdílený ostatními dvěma.

Chceme, aby věci byly jednoduché. Proto se plugin bude skládat z následujícího minimálního rozhraní:

{
  name: String,
  run: () => String
}

Pokud jste si Meteor ještě nenainstalovali, můžete si jej nainstalovat nyní, což zabere jen minutu nebo dvě.

Krok 1 – Vytvořte projekt a balíčky

Vytvoření projektu a balíčků je hotovo během okamžiku:

$ meteor create plugin-example
$ cd plugin-example
$ meteor npm install
$ mkdir -p packages
$ cd packages
$ meteor create --package plugin-loader
$ meteor create --package hello-plugin

Jakmile je vytvoříte, musíte do projektu přidat oba balíčky:

$ cd ..
$ meteor add plugin-loader hello-plugin

Nyní je vše nastaveno a můžeme začít implementovat plugin-loader , první.

Krok 2 – Implementujte plugin-loader

Samotný nakladač pluginů také není příliš složitý. Jeho jediná funkce je definována následovně:

  • zaregistrujte plugin pod zadaným názvem a funkcí načtení, kde název odlišuje plugin od ostatních a funkce načtení skutečně načte plugin do hostitelské aplikace
  • načíst všechny pluginy provedením všech registrovaných funkcí načítání a vrátit pole všech načtených pluginů

Pro implementaci používáme jednoduchou mapu k ukládání dat a poskytujeme pouze dvě funkce pro přístup:

packages/plugin-loader/plugin-loader.js

export const PluginLoader = {}

/** internal store of load functions **/
const plugins = new Map()

/**
 * Add a plugin to the loader.
 * @param key {String} the plugin name, prevent duplicates
 * @param load {aync Function} imports the actual plugin
 */
PluginLoader.add = (key, load) => {
  plugins.set(key, load)
}

/**
 * Load all registered plugins. Could be extended by a filter.
 * @return {Promise} a promise that resolves to an array of all loaded plugins
 */
PluginLoader.load = () => {
  const values = Array.from(plugins.values())
  plugins.clear()
  return Promise.all(values.map(fct => fct()))
}

To je vše pro zavaděč pluginů. Ostatní soubory v balíčku můžete ponechat tak, jak jsou, a přejít k dalšímu kroku.

Krok 3 – Implementace pluginu

Toto je nejkritičtější část, protože se předpokládá správné využití zavaděče zásuvných modulů, aby nedošlo k načtení zásuvných modulů do původního klientského balíčku. Soustřeďte se, protože po jednotlivých krocích vysvětlím věci podrobně.

Začněme s naším pluginem samotným, který by měl při zavolání jednoduše vrátit nějakou uvítací zprávu:

packages/hello-plugin/hello-plugin.js

const HelloPlugin = {}

HelloPlugin.name = 'helloPlugin'

HelloPlugin.run = function () {
  return 'Hello from a plugin'
}

;(function () {
  // if you see this line at startup then something went wrong
  console.info('plugin loaded')
})()

module.exports = HelloPlugin

Nic převratného, ​​ale teď musíme vytvořit nový soubor , který zaregistruje plugin do zavaděče:

packages/hello-plugin/register.js

import { PluginLoader } from 'meteor/plugin-loader'

PluginLoader.add('helloPlugin', async function () {
  // await import(...) import other dependencies
  // from this package, if necessary
  return import('./hello-plugin')
})

Tím se ve skutečnosti nezaregistruje plugin, ale asynchronní funkce, která se sama používá k volání dynamického importu pluginu (a dalších souborů z tohoto balíčku, pokud je to nutné).

Upozornění: Pokud přímo použijete import('./hello-plugin') okamžitě naimportuje plugin, což zde nechceme.

Nakonec, abychom mohli plugin "automaticky" zaregistrovat, musíme provést malou změnu v package.js takže to vypadá následovně:

packages/hello-plugin/package.js

Package.onUse(function (api) {
  api.versionsFrom('1.12.1')
  api.use('ecmascript')
  api.use('plugin-loader')
  api.addFiles('register.js')
})

Funguje to, protože api.addFiles nejenže přidá soubor do počátečního klientského balíku, ale také zajistí, že kód v něm bude spuštěn při spuštění klienta. Protože jsme však odstranili api.mainModule volání a nemají žádný další odkaz na hello-plugin.js kromě dynamického importu nebude tento soubor přidán, dokud jej nenačte zavaděč.

Nyní můžeme oba balíčky integrovat do naší aplikace v dalším kroku.

Krok 4 – Nahrání pluginu na vyžádání

Abychom byli co nejméně, zaměříme se zde pouze na klienta. Proto provedeme změny pouze v client/ složka.

Na základě počátečního main.js importujeme zavaděč zásuvných modulů a vytvoříme nějakou reaktivní proměnnou, která bude indikovat, zda jsme nahráli pluginy nebo ne.

client/main.js

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { PluginLoader } from 'meteor/plugin-loader'
import './main.html';

const loadedPlugins = new Map()

Template.hello.onCreated(function helloOnCreated() {
  const instance = this
  instance.loaded = new ReactiveVar(false)
})

Template.hello.helpers({
  plugins () {
    return Array.from(loadedPlugins.values())
  },
  loaded () {
    return Template.instance().loaded.get()
  }
})

...

Poté přidáme tlačítko, na jehož akci vlastně pluginy načteme pomocí loaderu:

client/main.js

...

Template.hello.events({
  'click .load-button': async function (event, instance) {
    const allPlugins = await PluginLoader.load()

    allPlugins.forEach(plugin => {
      loadedPlugins.set(plugin.name, plugin)
    })

    instance.loaded.set(true)
  }
})

Od PluginLoader.load vrátí Promise<Array> (přes Promise.all ) můžeme použít async/await aby byl kód čitelný.

Když jsou všechny pluginy načteny, můžeme je jednoduše uložit do datové struktury (jako je mapa, použitá v příkladu) a poté nastavit reaktivní proměnnou loaded na true takže to způsobí, že šablona vykreslí naše pluginy.

Všimněte si, že pluginy nemůžete přímo ukládat do reaktivní proměnné, protože mohou ztratit své funkce, aby fungovaly.

A konečně, šablona není nic přepychového a měla by vypadat následovně:

client/main.html

<head>
  <title>plugin-example</title>
</head>

<body>
  <h1>Plugins example</h1>

  {{> hello}}
</body>

<template name="hello">
    {{#if loaded}}
        {{#each plugin in plugins}}
            {{plugin.name}}: {{plugin.run}}
        {{/each}}
    {{else}}
        <button class="load-button">Load plugins</button>
    {{/if}}
</template>

Vše hotovo a připraveno ke spuštění. 🚀

Krok 5 – spuštění kódu

Ve svém projektu můžete zadat meteor příkaz pro spuštění kódu:

$ cd /path/to/plugin-example
$ meteor

Poté otevřete http://localhost:3000/ a měli byste vidět něco takového:

V tomto okamžiku by vaše konzole prohlížeče (F12) neměla!!! vytiskli "plugin loaded"

Nyní klikněte na tlačítko a načtěte plugin. Nyní byste měli vidět výstup pluginu:

Kromě toho by nyní v konzole vašeho prohlížeče měla být "plugin loaded" byly vytištěny.

🎉 Gratulujeme, vytvořili jste počáteční základ pro jednoduchou architekturu pluginů v Meteoru.

Shrnutí a výhled

V tomto tutoriálu jsme položili základy psaní zásuvného softwaru pomocí jednoduchého mechanismu zavádění zásuvných modulů.

V budoucích tutoriálech bychom se mohli zaměřit na rozhraní pluginu, jak interaguje s hostitelskou aplikací a jak můžeme využít některé ze základních funkcí Meteoru (Mongo, Authentication, Methods, Pub/Sub), abychom usnadnili vývoj pluginů.


Pravidelně zde na dev.to publikuji články o Meteoru a JavaScript . Pokud se vám líbí, co čtete a chcete mě podpořit, můžete mi poslat tip přes PayPal.

Můžete mě také najít (a kontaktovat) na GitHubu, Twitteru a LinkedIn.

Sledujte nejnovější vývoj na Meteoru tím, že navštívíte jejich blog, a pokud jste do Meteoru stejní jako já a chcete to ukázat světu, měli byste se podívat do obchodu Meteor s merch.