Jak generujeme naši novou dokumentaci pomocí Sanity &Nuxt.js

Posledních několik měsíců jsme strávili vytvářením nové verze našeho nákupního košíku.

Když jsme na tom začali pracovat, věděli jsme, že to bude znamenat změny i v jiných oblastech našeho produktu.

Dokumentace byla jedním z nich.

Znamenalo to několik konkrétních a velmi potřebných upgradů:

  • Vylepšení navigace mezi verzemi dokumentů

  • Přehodnocení stromové struktury obsahu

  • Co nejvíce automatizovat generování dokumentace

Také jsme chtěli zůstat věrní tomu, co kážeme; pomocí JAMstack! To znamenalo vybrat si správné nástroje JavaScript pro generování naší dokumentace.

Nakonec jsme vybrali Nuxt pro generování statické dokumentace, Sanity.io pro správu obsahu a Netlify pro automatizované nasazení. Později vysvětlím proč.

Nakonec to byla skvělá příležitost, jak výrazně vylepšit uživatelské prostředí dokumentů pro uživatele i náš vývojový tým.

V tomto příspěvku vám chci ukázat, jak jsme to udělali a jak to můžete replikovat.

Vytváření naší dokumentace (trochu kontextu)

Náš starý dokument byl vytvořen s vlastním Node.js a potřeboval vykreslování na straně serveru při každém novém načtení stránky. Často jsme zapomínali dokumentovat nové opravy a jednoduché funkce. Čas od času se vyskytly i nešťastné chyby a překlepy. Stručně řečeno, dokumentace se často může stát nepříjemnou záležitostí. Jsem si jistý, že to někteří z vás umí.

Takže pro naši novou dokumentaci jsme si stanovili několik cílů. Muselo:

  • Být nasazen jako plně statický web

  • Být hostován na rychlém CDN

  • Používejte Vue.js na frontendu (je to základní rámec našeho týmu)

  • Usnadněte úpravy obsahu celému týmu – nejen vývojářům!

  • Zajistěte, aby byly všechny metody našeho Javascript API a přepsatelné komponenty motivu řádně zdokumentovány

Tato kombinace kritérií se přidala k jasné volbě zásobníku:generátor statických stránek poháněný Vue připojený k bezhlavému CMS.

Jako fanoušci automatizace jsme nechtěli spravovat dokumentaci komponent našeho tématu a rozhraní Javascript API nezávisle. Data dokumentace by bylo nutné generovat v době sestavování z kódu a komentářů JSDoc.

To by vyžadovalo značné množství další práce, ale z dlouhodobého hlediska zajistěte, aby dokumentace byla vždy aktuální a ověřená ve stejnou dobu, kdy kontrolujeme požadavky na stažení funkcí.

To také přidalo omezení výběru bezhlavého CMS s výkonným API pro aktualizaci obsahu.

Proč Sanity jako bezhlavý CMS?

Existuje mnoho, mnoho bezhlavých CMS. Doporučuji provést důkladný průzkum a změřit klady a zápory, než si nějaký vyberete. V našem případě existuje několik kritérií, díky kterým se bilance přiklonila ve prospěch Sanity.io:

  • Skvělé okamžité úpravy

  • Plně hostované – není potřeba to spravovat v naší infrastruktuře

  • Open source a přizpůsobitelné

  • Vynikající API pro dotazování i psaní

  • Webhooky, které nám umožňují znovu sestavit dokument po úpravách obsahu

Zahájení projektu Sanity je jednoduché. V nově vytvořeném úložišti spusťte sanity init .

Poté definujte několik typů dokumentů, a pokud to chcete mít, vytvořte si vlastní komponenty, které přizpůsobí úpravy vašim konkrétním potřebám. I když se pustíte do šílenství s přizpůsobením, nezabrání vám to nasadit váš CMS na Sanity – to je místo, kde to skutečně září, protože vysoká přizpůsobitelnost je u hostovaných řešení poměrně vzácnou vlastností.

Sanity’s API bylo také závanem čerstvého vzduchu.

GROQ, jejich dotazovací jazyk, je vítaným přírůstkem do ekosystému. Přemýšlejte o GraphQL, aniž byste museli být vždy explicitní ohledně všech polí, která chcete v dotazu (nebo abyste byli schopni dotazovat se na polymorfní data, aniž byste si připadali jako Hercules Labors).

Kromě toho lze úpravy zahrnout do rozsahu transakce, což nám umožňuje dávkové aktualizace více dokumentů z našeho procesu sestavení tématu a sady SDK. Zkombinujte to s webhooky a zajistíte, že nasazení dokumentace spustíme pouze jednou pro mnoho změn z našich repozitářů motivů a SDK.

Proč Nuxt jako generátor statických stránek?

Právě když jste si mysleli, že je na výběr spousta bezhlavých CMS, narazíte na desítky existujících SSG.

Hlavní požadavky na náš generátor statických stránek byly:

  • Nasazuje pouze statické soubory

  • Používá Vue.js

  • Načítá data z externího rozhraní API

Použití Vue.js se zde může zdát svévolné a měli byste se správně zeptat:„Proč nereagovat nebo něco jiného?“ Upřímně řečeno, zpočátku to bylo trochu svévolné, protože to odpovídá osobním preferencím týmu, ale jak stavíme stále více projektů, oceňujeme také konzistenci napříč všemi z nich.

V řídicím panelu jsme dlouho používali Vue.js a pustili jsme se naplno do našeho výchozího motivu v3.0. Tato konzistence nám nakonec umožní nejen rychlejší začlenění členů týmu, ale také opětovné použití kódu. Řekněme, že bychom chtěli vytvořit živý náhled přizpůsobení tématu; sdílení stejného zásobníku mezi dokumenty a motivem to usnadňuje.

Jak již bylo řečeno, zůstali nám tři uchazeči o SSG:VuePress, Nuxt &Gridsome.

VuePress . Mít vestavěnou podporu pro inline komponenty Vue v obsahu bylo opravdu lákavé, ale bez možnosti klepnout na externí zdroj dat místo místních souborů markdown to nešlo.

→ Nuxt.js. Toto je výkonný kůň vývoje SPA s Vue. Nabízí skvělou strukturu a ty správné prodlužovací body, aby byly skutečně flexibilní. nuxt generate příkaz umožňuje nasadit plně statickou a předem vykreslenou verzi webu. Vytvoření obsahově řízeného webu místo dynamické webové aplikace však vyžaduje další práci.

Gridsome . Je přímo inspirován Gatsbym, má prvotřídní podporu pro externí zdroje dat a byl vytvořen za účelem vytváření statických webových stránek z těchto dat. Poté, co jsem s ním již experimentoval, a protože zaškrtl všechna políčka, Gridsome se nejprve zdál jako vyvolený.

Rychle jsme však narazili na některé bolestivé body:

  • Automatické generování schématu GraphQL má určité problémy a často vyžaduje zadat typ polí ručně.

  • Nemohli jsme strukturovat naše data, jak jsme chtěli. Museli jsme uložit function , class a enum , které všechny musely být spojeny se stránkami dokumentace polymorfním způsobem.

  • Buďme upřímní, nutnost zabývat se schématem GraphQL jednoduše zpomaluje iterační cykly.

Celkově Gridsome postrádal trochu vyspělosti, pokud jde o komplexní schéma. Pokud jde o GraphQL, vyniká ve scénářích, kde máte více spotřebitelů dat, kteří se zajímají o různé dotazy. V našem případě to jen přidalo zbytečné kroky.

Nakonec jsme se rozhodli použít Nuxt a chybějící části vyvinout ručně.

Jediné, co v tuto chvíli chybí, je něco k nasazení naší dokumentace. Pro nás k žádné debatě nedošlo. Netlify je tady jednoznačné, takže se stalo posledním chybějícím kouskem v našem stacku.

Naše nová generace dokumentace, styl Javascript

Než se ponoříme do technických drobností, podívejme se na ten stoh propojený dohromady. Projekty JAMstack se mohou někdy zdát ohromující kvůli množství použitých nástrojů, ale umožňuje vám je vybrat pro jejich konkrétní hodnotu.

I když jsou některé jednotlivé části poměrně složité, jejich skládání bylo celkem snadné.

Naše dokumentace se skládá z tradičních stránek s obsahem napsaných naším vývojářským nebo marketingovým týmem a technického obsahu extrahovaného ze dvou úložišť:

  • Dokument Javascript SDK (podobný našemu ručně vytvořenému Javascript API V2)

  • Dokument ke komponentám motivu Vue.js (novinka ve verzi 3.0 pro přepisování komponent)

Stránky obsahu se upravují přímo v Sanity CMS. Technický obsah se automaticky generuje pomocí rozhraní API kompilátoru Typescript a při aktualizaci každého úložiště je odeslán do rozhraní API Sanity ve skriptu na našem CI. Tento skript používá transakční funkci Sanity k aktualizaci všech úprav najednou.

Změny od Sanity generují webhook, který používáme ke spuštění sestavení na Netlify. Manipulace s webhooky v nastavení JAMstack často vyžaduje použití nějakého druhu funkce Lambda jako logické vrstvy mezi webhookem zdroje a rozhraním API cíle.

Zde však můžeme využít chytré předvídavosti od Netlify. Jejich příchozí koncový bod webhooku je jednoduchá soukromá adresa URL, která přijímá jakýkoli požadavek POST ke spuštění sestavení – což znamená, že webhook společnosti Sanity lze nakonfigurovat přímo na něj!

Jakmile je sestavení spuštěno, spustí se nuxt generate . Náš vlastní kód načítá data ze Sanity a dist složka bude nasazena na bleskově rychlém CDN.

Stručně řečeno, Sanity se používá jako úložiště všeho, co je v našich dokumentech potřeba. Samotná dokumentace je vždy aktuální se vším, co se uvolní do výroby. Dokumentaci pocházející ze zdrojů lze ověřit jako součást běžného procesu kontroly kódu.

Generování dokumentace ze zdrojů

Všechny naše projekty v3.0 jsou v Typescriptu, což nám umožňuje využívat jeho API kompilátoru k extrahování dokumentace ze zdrojového kódu. To se děje ve třech fázích:

  1. Kompilátor automaticky generuje definice typů (a .d.ts soubor) projektu s výjimkou všech typů označených jako interní (pomocí @internal). tagy v komentářích JSDoc). Toho lze dosáhnout jednoduše nastavením declaration a stripInternal na true v našem tsconfig.json

  2. Náš vlastní skript je spuštěn; přečte .d.ts soubor, analyzujte jej pomocí rozhraní API kompilátoru a výsledek předá knihovně nazvané readts, která převede výstup kompilátoru do lépe spravovatelné datové struktury.

  3. Nakonec náš skript aktualizuje databázi Sanity pomocí jejich modulu npm.

Vezměme si tuto funkci jako příklad:

/**
 * Initialize the SDK for use in a Web browser
 * @param apiKey Snipcart Public API Key
 * @param doc Custom document node instead of `window.document`
 * @param options Initialization options
 */
export async function initializeBrowserContext(
        apiKey?: string,
        doc?: HTMLDocument,
        options?: SnipcartBrowserContextOptions) : Promise<SDK> {
  // some internal code
}

Exportuje se v deklaraci typu naší sady SDK téměř tak, jak je, bez těla metody. Následující kód nám umožňuje převést jej strukturovaným způsobem:

const parser = new readts.Parser();
parser.program = ts.createProgram(["snipcart-sdk.d.ts"]);
parser.checker = parser.program.getTypeChecker();
parser.moduleList = [];
parser.symbolTbl = {};
    
// the compiler will load any required typescript libs
// but we only need to document types from our own project
const source = parser.program
    .getSourceFiles()
    .filter(s => s.fileName === "snipcart-sdk.d.ts")[0];
    
// we instruct `readts` to parse all
// `declare module 'snipcart-sdk/*' {...}` sections
for (const statement of source.statements) {
    parser.parseSource(statement);
}
    
const result = parser.moduleList.map((module) => {
  /* some more transformations */
});

Po nahrání do datové sady Sanity bude předchozí deklarace funkce vypadat takto:

{
    "_id": "sdk-contexts-browser-initializeBrowserContext",
    "_type": "sdk-item",
    "kind": "function",
    "name": "initializeBrowserContext",
    "signatures": [
        {
            "doc": "Initialize the SDK for use in a Web browser",
            "params": [
                {
                    "doc": "Snipcart Public API Key",
                    "name": "apiKey",
                    "optional": true,
                    "type": {
                        "name": "string"
                    }
                },
                /* other params */
            ],
            "returnType": {
                "id": "sdk-core-SDK",
                "name": "SDK"
            },
        }
    ]
}

Pomocí readts to může vypadat jako procházka růžovým sadem, ale použití API kompilátoru Typescript není pro slabé povahy. Často se budete muset ponořit do symbolů kompilátoru (nezaměňovat se symboly z jazyka), uzlů AST a jejich SyntaxKind enum hodnoty.

Data jsou nyní připravena ke spotřebě naším SSG, podívejme se, jak jsme propojili Nuxt!

Učinit Nuxt plně statickým a zaměřeným na obsah

Prostřednictvím jeho nuxt generate Nuxt.js dokáže vytvořit plně statický web v době sestavování.

Na rozdíl od Gatsby nebo Gridsome, které ukládají obsahové uzly do mezipaměti, se načítání dat stále provádí i ve statickém režimu s Nuxtem. Stává se to proto, že asyncData metoda se volá vždy a je na vývojáři, aby v případě potřeby poskytl odlišnou logiku. V komunitě Nuxt se již mluví o nápravě tohoto problému. Ale potřebovali jsme to HNED 🙂

K tomuto problému jsme přistoupili pomocí modulu Nuxt, který se při volání z klienta (statický web) nebo ze serveru (když nuxt generate) chová jinak je nazýván). Tento modul je deklarován v našem nuxt.config.js :

modules: [
    "~/modules/data-source",
],

Poté jednoduše zaregistruje serverový a klientský plugin:

export default async function DataSourceModule (moduleOptions) {
    this.addPlugin({
        src: path.join(__dirname, 'data-source.client.js'),
        mode: 'client',
    });
    this.addPlugin({
        src: path.join(__dirname, 'data-source.server.js'),
        mode: 'server',
    });
}

Oba vystavují stejnou metodu načítání dat na každé součásti stránky. Liší se tím, že na serveru tato metoda přímo volá Nuxt API pro načtení obsahu:

// data-source.server.js
import { loadPageByUrl } from '~/sanity.js';
    
export default (ctx, inject) => {
    inject('loadPageData', async () => {
        return await loadPageByUrl(ctx.route.path);
    });
}

Na klientovi plugin místo toho načte statický soubor JSON:

// 'data-source.client.js'
import axios from 'axios';
    
export default (ctx, inject) => {
    inject('loadPageData', async () => {
        const path = '/_nuxt/data' + ctx.route.path + '.json';
        return (await axios(path)).data;
    });
}

Nyní v komponentě naší stránky můžeme slepě volat loadPageData a modul a pluginy zaručí, že se použije správná verze:

<!-- page.vue -->
<template>
  <Markdown :content="page && page.body || ''" />
</template>
<script>
import Markdown from '~/components/Markdown';
    
export default {
    props: ['page'],
    components: {
        Markdown,
    },
    async asyncData() {
        return await app.$loadPageData();
    }
}
</script>

Zde je ukázka toho, jak funkce, o které jsem mluvil dříve, vypadá v dokumentu:

Konečný výsledek

Úvahy na závěr

Začít na Sanity bylo hračkou, a i když jsme to ještě nedotáhli daleko, vše vypadá účelně postavené tak, aby se dalo hladce rozšířit. Byl jsem opravdu ohromen jejich API, dotazováním pomocí GROQ a tím, jak lze vytvořit pluginy pro CMS.

Pokud jde o Nuxt, i když to vyžadovalo více práce pro náš případ použití, stále poskytuje silnou základnu pro vytvoření jakéhokoli projektu Vue.js.

Po všech těch křupavých základech jsme připraveni pustit se do dalších kosmetických vylepšení dokumentace, jako je lepší objevitelnost a organizace našich metod SDK.

Pokud se vám tento příspěvek líbil, věnujte prosím chvilku jeho sdílení na Twitteru . Máte připomínky, dotazy? Klikněte na sekci níže!