Mars Theme: Ein tiefer Einblick in Frontitys Headless WordPress Theme

Avatar of Ganesh Dahal
Ganesh Dahal am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Dieser Beitrag war in Arbeit, bevor Automattic Frontity und sein gesamtes Team übernommen hat. Laut den Gründern von Frontity wird das Framework in ein von der Community geführtes Projekt überführt und das Projekt in „einer stabilen, fehlerfreien Position“ mit Dokumentation und Funktionen belassen. Wie andere Open-Source-Community-Projekte bleibt Frontity kostenlos, so wie es war, mit Möglichkeiten, zum Projekt beizutragen und es zu einem noch besseren Framework für entkoppeltes WordPress zu machen. Weitere Details finden Sie auf dieser FAQ-Seite.

In meinem vorherigen Artikel haben wir eine Headless WordPress-Site mit Frontity erstellt und uns kurz die Dateistruktur angesehen. In diesem begleitenden Artikel werden wir tief in das Paket @frontity/mars-theme oder Mars Theme eintauchen, mit einer schrittweisen Anleitung, wie man es anpasst, um unser eigenes zu erstellen. Das Mars Theme ist nicht nur ein großartiger Einstieg, sondern auch das Standard-Theme von Frontity – so etwas wie WordPress Twenty Twenty-One oder ähnliches. Das macht es zu einem perfekten Ausgangspunkt für uns, um praktische Erfahrungen mit Frontity und seinen Funktionen zu sammeln.

Insbesondere werden wir uns die grundlegenden Teile des Mars Theme von Frontity ansehen, einschließlich dessen, was sie „Bausteine“ nennen, sowie die verschiedenen Komponenten, die mit dem Paket geliefert werden. Wir werden behandeln, was diese Komponenten tun, wie sie funktionieren und schließlich, wie Styling mit Beispielen funktioniert.

Bereit? Los geht’s!


Frontitys Bausteine

Schauen wir uns noch einmal die Dateistruktur des Frontity-Projekts an, das wir im letzten Artikel erstellt haben, da uns dies genau zeigt, wo sich Frontitys Bausteine, die Datei frontity.settings.js und der Ordner package.json und packages/mars-theme befinden. Wir haben diese bereits ausführlich behandelt, aber insbesondere die Datei package.json gibt uns viele Informationen über das Projekt, wie den Namen, die Beschreibung, den Autor, Abhängigkeiten usw. Hier ist, was diese Datei enthält:

  • frontity: Dies ist das Hauptpaket, das alle Methoden enthält, die bei der Entwicklung von Frontity-Apps verwendet werden. Hier befindet sich auch die CLI.
  • @frontity/core: Dies ist das wichtigste Paket, da es sich um das Bündeln, Rendern, Zusammenführen, Transpilieren, Bereitstellen usw. kümmert. Wir müssen nicht darauf zugreifen, um eine Frontity-App zu entwickeln. Die vollständige Liste ist in den Frontity-Dokumenten erfasst.
  • @frontity/wp-source: Dieses Paket verbindet sich mit der WordPress REST API unserer Site und ruft alle im Mars Theme benötigten Daten ab.
  • @frontity/tiny-router: Dieses Paket verarbeitet window.history und hilft uns beim Routing.
  • @frontity/htmal2react: Dieses Paket konvertiert HTML in React und arbeitet mit Prozessoren, die mit HTML-Teilen übereinstimmen und diese durch React-Komponenten ersetzen.

Der Frontity-Kern oder @frontity/package (auch als Frontitys Baustein bezeichnet) besteht aus nützlichen React-Komponentenbibliotheken in seinem Paket @frontity/components, das hilfreiche Dinge wie Link, Auto Prefetch, Image, Props, Iframe, Switch und andere Funktionen, Objekte usw. exportiert, die direkt in Frontity-Projektkomponenten importiert werden können. Eine detailliertere Beschreibung dieser Komponenten – einschließlich Syntaxinformationen und Anwendungsfällen – finden Sie in dieser Paketreferenz-API.

Die Frontity-Dokumentation bietet weitere Informationen darüber, was beim Starten eines Frontity-Projekts passiert:

Beim Starten von Frontity werden alle in frontity.settings.js definierten Pakete von @frontity/file-settings importiert, und die Einstellungen und Exporte jedes Pakets werden von @frontity/core zu einem einzigen Store zusammengeführt, in dem Sie während der Entwicklung mithilfe von @frontity/connect, dem Frontity State Manager, auf den state und die actions der verschiedenen Pakete zugreifen können.

Als Nächstes machen wir uns damit vertraut, wie diese Bausteine, Dienstprogramme und Exporte im Mars Theme-Paket verwendet werden, um ein funktionierendes Frontity-Projekt mit einem Headless WordPress-Endpunkt zu erstellen.

Abschnitt 1: Ein tiefer Blick in das Mars Theme

Bevor wir über Styling und Anpassung sprechen, machen wir uns kurz mit der Struktur des Mars Theme (@frontity/mars-theme) vertraut und wie es zusammengestellt ist.

#! frontity/mars-theme file structure
packages/mars-theme/
|__ src/
  |__ index.js
  |__ components/
     |__ list/
       |__ index.js
       |__ list-item.js
       |__ list.js
       |__ pagination.js
     |__ featured-media.js
     |__ header.js
     |__ index.js
     |__ link.js
     |__ loading.js
     |__ menu-icon.js
     |__ menu-model.js
     |__ menu.js
     |__ nav.js
     |__ page-error.js
     |__ post.js
     |__ title.js

Das Mars Theme verfügt über drei wichtige Komponentendateien: /src/index.js, src/list/index.js und src/components/index.js. Die Dokumentation von Frontity ist eine großartige Ressource, um das Mars Theme zu verstehen, mit besonders detaillierten Informationen darüber, wie verschiedene Mars Theme-Komponenten in einer Frontity-Site definiert und miteinander verbunden werden. Beginnen wir damit, uns mit den drei wichtigsten Komponenten des Themes vertraut zu machen: Root, Theme und List.

Theme Root-Komponente (/src/index.js)

Die Datei src/index.js, auch bekannt als das Root des Themes, ist eine der wichtigsten Mars Theme-Komponenten. Das Root dient als Einstiegspunkt, der <div id="root"> im Site-Markup anspricht, um die Roots aller installierten Pakete, die zum Ausführen eines Frontity-Projekts erforderlich sind, einzufügen. Ein Frontity-Theme exportiert ein root und andere erforderliche Pakete im DOM, wie im folgenden Anwendungsbeispiel aus der Frontity-Dokumentation gezeigt:

<!-- /index.HTML (rendered by Frontity) -->
<html>
  <head>...</head>
  <body>
    <div id="root">
      <MyAwesomeTheme />
      <ShareModal />
      <YetAnotherPackage />
    </div>
  </body>
</html>

Dieses Frontity-Dokument erklärt, wie Frontity sein Theme mithilfe von Erweiterungsmustern namens Slot und Fill erweitert. Ein Beispiel für die Root-Komponente (/src/index.js) stammt aus dem Mars Theme-Paket (@frontity/mars-theme).

Dies ist alles, was das Paket beim Initialisieren der Root-Komponente abruft:

// mars-theme/src/components/index.js
import Theme from "./components";
// import processor libraries
import image from "@frontity/html2react/processors/image";
import iframe from "@frontity/html2react/processors/iframe";
import link from "@frontity/html2react/processors/link";

const marsTheme = {
  // The name of the extension
  name: "@frontity/mars-theme",
  // The React components that will be rendered
  roots: {
    /** In Frontity, any package can add React components to the site.
      * We use roots for that, scoped to the `theme` namespace. */
    theme: Theme,
  },
  state: {
    /** State is where the packages store their default settings and other
      * relevant state. It is scoped to the `theme` namespace. */
    theme: {
      autoPrefetch: "in-view",
      menu: [],
      isMobileMenuOpen: false,
      featured: {
        showOnList: false,
        showOnPost: false,
      },
    },
  },

  /** Actions are functions that modify the state or deal with other parts of
    * Frontity-like libraries. */
  actions: {
    theme: {
      toggleMobileMenu: ({ state }) => {
        state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;
      },
      closeMobileMenu: ({ state }) => {
        state.theme.isMobileMenuOpen = false;
      },
    },
  },
  /** The libraries that the extension needs to create in order to work */
  libraries: {
    html2react: {
      /** Add a processor to `html2react` so it processes the `<img>` tags
        * and internal link inside the content HTML.
        * You can add your own processors too. */
      processors: [image, iframe, link],
    },
  },
};

export default marsTheme;

Die Mars Theme-Root-Komponente exportiert Pakete, die enthalten: alle roots, fills, state, actions und libraries-Elemente. Detailliertere Informationen zu Root finden Sie in diesem Frontity-Dokument.

Theme-Komponente (/src/components/index.js)

Die Frontity Theme-Komponente ist die wichtigste Root-Ebenen-Komponente, die vom Theme-Namespace exportiert wird (Zeilen 12-16, im vorherigen Beispiel hervorgehoben. Die Theme-Komponente ist mit der Funktion @frontity/connect (Zeile 51, unten hervorgehoben) umschlossen, die Zugriff auf ihre state-, actions- und libraries-Eigenschaften von der Root-Komponenteninstanz bietet und es der Theme-Komponente ermöglicht, den state zu lesen, über actions zu manipulieren oder Code aus anderen Feature-Paketen in den Bibliotheken zu verwenden.

// mars-theme/src/components/index.js
import React from "react"
// Modules from @emotion/core, @emotion/styled, css, @frontity/connect, react-helmet
import { Global, css, connect, styled, Head } from "frontity";
import Switch from "@frontity/components/switch";
import Header from "./header";
import List from "./list";
import Post from "./post";
import Loading from "./loading";
import Title from "./title";
import PageError from "./page-error";

/** Theme is the root React component of our theme. The one we will export
 * in roots. */
const Theme = ({ state }) => {
  // Get information about the current URL.
  const data = state.source.get(state.router.link);

  return (
    <>
      {/* Add some metatags to the <head> of the HTML with react-helmet */}
      <Title />
      <Head>
        <meta name="description" content={state.frontity.description} />
        <html lang="en" />
      </Head>

      {/* Add some global styles for the whole site, like body or a's. 
      Not classes here because we use CSS-in-JS. Only global HTML tags. */}
      <Global styles={globalStyles} />

      {/* Render Header component. Add the header of the site. */}
      <HeadContainer>
        <Header />
      </HeadContainer>

      {/* Add the main section. It renders a different component depending
      on the type of URL we are in. */}
      <Main>
        <Switch>
          <Loading when={data.isFetching} />
          <List when={data.isArchive} />
          <Post when={data.isPostType} />
          <PageError when={data.isError} />
        </Switch>
      </Main>
    </>
  );
};

export default connect(Theme);

{/* define Global styles and styled components used Theme component here */}
const globalStyles = css`
  body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
      "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  }
  a,
  a:visited {
    color: inherit;
    text-decoration: none;
  }
`;
const HeadContainer = styled.div`
  // ...
`;

const Main = styled.div`
  // ...
`;

Dieses Beispiel stammt direkt aus dem Mars Themes /src/components/index.js-Komponente, die wir mit connect von Frontity importiert haben (Zeile 4, oben). Wir verwenden state.source.get(), um data abzurufen, die vom aktuellen Pfad gerendert werden sollen (Zeilen 39-46, oben hervorgehoben); zum Beispiel List, Post und andere Komponenten.

Abschnitt 2: Arbeiten mit der List-Komponente

Was wir uns gerade angesehen haben, sind die Theme-Ebenen-Komponenten im Mars Theme von Frontity. Möglicherweise haben Sie bemerkt, dass diese Komponenten zusätzliche Komponenten importieren. Schauen wir uns eine davon genauer an, die List-Komponente.

Die List-Komponente wird von src/components/list/index.js exportiert, die @loadable/components verwendet, um den Code der List-Komponente so aufzuteilen, dass die Komponente nur geladen wird, wenn ein Benutzer auf eine Listenansicht klickt; andernfalls wird sie überhaupt nicht gerendert, z. B. wenn stattdessen auf eine Post-Ansicht geklickt wird.

// src/components/list/index.js
import { loadable } from "frontity";

// Codesplit the list component so it's not included if the users
// load a post directly.
export default loadable(() => import("./list"));

In diesem Beispiel verwendet Frontity loadble-Funktionen (integriert aus Loadable components) für Code-Splitting, das eine Komponente asynchron lädt und Code in verschiedene Bundles aufteilt, die zur Laufzeit dynamisch geladen werden. Die API-Referenz des Frontity-Kernpakets geht viel detaillierter darauf ein.

Anzeigen von Beitragslisten

Um eine Liste von Beiträgen auf einer Archivseite anzuzeigen, müssen wir uns zuerst die Frontity-Komponente src/components/list/list.js ansehen. Wie der Name schon sagt, rendert die List-Komponente Listen von Beiträgen mithilfe von state.source.get(link) und dessen items-Feld (Zeilen 22-25, unten hervorgehoben).

// src/components/list/list.js
import { connect, styled, decode } from "frontity";
import Item from "./list-item";
import Pagination from "./pagination";

const List = ({ state }) => {
  // Get the data of the current list.
  const data = state.source.get(state.router.link);
  return (
    <Container>
      {/* If the list is a taxonomy, we render a title. */}
      {data.isTaxonomy && (
        <Header>
          {data.taxonomy}: {state.source[data.taxonomy][data.id].name}
        </Header>
      )}
      {/* If the list is an author, we render a title. */}
      {data.isAuthor && (
        <Header>Author: {state.source.author[data.id].name}</Header>
      )}
      {/* Iterate over the items of the list. */}
      {data.items.map(({ type, id }) => {
        const item = state.source[type][id];
        // Render one Item component for each one.
        return <Item key={item.id} item={item} />;
      })}
      <Pagination />
    </Container>
  );
};
export default connect(List);

Im Codebeispiel oben wird die Funktion connect in Zeile 2 von Frontity importiert und in Zeile 31 (der letzten Zeile) um die exportierte connect(List)-Komponente gewickelt. Zwei weitere Komponenten, list-item.js und pagination.js, werden ebenfalls importiert. Schauen wir uns diese als Nächstes an!

Hier ist, was wir für list-item.js haben:

// src/components/list/list-item.js
import { connect, styled } from "frontity";
import Link from "../link";
import FeaturedMedia from "../featured-media";

const Item = ({ state, item }) => {
  const author = state.source.author[item.author];
  const date = new Date(item.date);
  return (
    <article>
     {/* Rendering clickable post Title */}
      <Link link={item.link}>
        <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} />
      </Link>
      <div>
        {/* If the post has an author, we render a clickable author text. */}
        {author && (
          <StyledLink link={author.link}>
            <AuthorName>
              By <b>{author.name}</b>
            </AuthorName>
          </StyledLink>
        )}
        {/* Rendering post date */}
        <PublishDate>
          {" "}
          on <b>{date.toDateString()}</b>
        </PublishDate>
      </div>
      {/* If the want to show featured media in the
       * list of featured posts, we render the media. */}
      {state.theme.featured.showOnList && (
        <FeaturedMedia id={item.featured_media} />
      )}
      {/* If the post has an excerpt (short summary text), we render it */}
      {item.excerpt && (
        <Excerpt dangerouslySetInnerHTML={{ __html: item.excerpt.rendered }} />
      )}
    </article>
  );
};
// Connect the Item to gain access to `state` as a prop
export default connect(Item);

Die Item-Komponente rendert die Vorschau eines Blogbeitrags mit anklickbarem Beitragstitel (Zeilen 12-14, oben hervorgehoben), Autorennamen (Zeilen 19-21, oben hervorgehoben) und Veröffentlichungsdatum (Zeilen: 25-28, oben hervorgehoben) zusammen mit <FeaturedMedia />, das als optionales Beitragsbild dient.

Paginieren einer Beitragsliste

Schauen wir uns die Pagination-Komponente an, die zuvor in der List-Komponente von src/components/list/pagination/js gerendert wurde:

// src/components/list/pagination.js
import { useEffect } from "react";
import { connect, styled } from "frontity";
import Link from "../link";

const Pagination = ({ state, actions }) => {
  // Get the total posts to be displayed based for the current link
  const { next, previous } = state.source.get(state.router.link);
  // Pre-fetch the the next page if it hasn't been fetched yet.
  useEffect(() => {
    if (next) actions.source.fetch(next);
  }, []);
  return (
    <div>
      {/* If there's a next page, render this link */}
      {next && (
        <Link link={next}>
          <Text>← Older posts</Text>
        </Link>
      )}
      {previous && next && " - "}
      {/* If there's a previous page, render this link */}
      {previous && (
        <Link link={previous}>
          <Text>Newer posts →</Text>
        </Link>
      )}
    </div>
  );
};
/**
 * Connect Pagination to global context to give it access to
 * `state`, `actions`, `libraries` via props
 */
export default connect(Pagination);

Die Pagination-Komponente wird verwendet, damit Benutzer zwischen Beitragslisten paginieren können – Sie wissen schon, wie man von Seite 1 zu Seite 2 navigiert oder von Seite 2 zurück zu Seite 1. Die state-, actions- und libraries-Eigenschaften werden durch den globalen Kontext bereitgestellt, der sie mit connect(Pagination) umschließt und exportiert.

Anzeigen einzelner Beiträge

Die Post-Komponente zeigt sowohl einzelne Beiträge als auch Seiten an. Tatsächlich sind beide strukturell gleich, außer dass wir bei Beiträgen normalerweise Metadaten (Autor, Datum, Kategorien usw.) anzeigen. Metadaten werden auf Seiten normalerweise nicht verwendet.

In dieser Post-Komponente werden bedingte Anweisungen nur gerendert, wenn das post-Objekt Daten enthält (d. h. data.isPost) und ein Beitragsbild in sate.theme.featured in der Root-Komponente des Themes ausgewählt ist:

// src/components/post.js
import { useEffect } from "react";
import { connect, styled } from "frontity";
import Link from "./link";
import List from "./list";
import FeaturedMedia from "./featured-media";

const Post = ({ state, actions, libraries }) => {
  // Get information about the current URL.
  const data = state.source.get(state.router.link);
  // Get the data of the post.
  const post = state.source[data.type][data.id];
  // Get the data of the author.
  const author = state.source.author[post.author];
  // Get a human readable date.
  const date = new Date(post.date);
  // Get the html2react component.
  const Html2React = libraries.html2react.Component;

  useEffect(() => {
    actions.source.fetch("/");
    {/* Preloading the list component which runs only on mount */}
    List.preload();
  }, []);

  // Load the post, but only if the data is ready.
  return data.isReady ? (
    <Container>
      <div>
        <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
        {/* Only display author and date on posts */}
        {data.isPost && (
          <div>
            {author && (
              <StyledLink link={author.link}>
                <Author>
                  By <b>{author.name}</b>
                </Author>
              </StyledLink>
            )}
            <DateWrapper>
              {" "}
              on <b>{date.toDateString()}</b>
            </DateWrapper>
          </div>
        )}
      </div>
      {/* Look at the settings to see if we should include the featured image */}
      {state.theme.featured.showOnPost && (
        <FeaturedMedia id={post.featured_media} />
      )}
      {/* Render the content using the Html2React component so the HTML is processed
       by the processors we included in the libraries.html2react.processors array. */}
      <Content>
        <Html2React html={post.content.rendered} />
      </Content>
    </Container>
  ) : null;
};
{/* Connect Post to global context to gain access to `state` as a prop. */} 
export default connect(Post);

Wir haben gerade gesehen, wie wichtig die List-Komponente ist, wenn es darum geht, eine Gruppe von Beiträgen anzuzeigen. Es ist das, was wir mit dem Markup korrelieren könnten, das wir im Allgemeinen verwenden, wenn wir mit dem WordPress-Loop für Archivseiten, Feeds der neuesten Beiträge und anderen Beitragslisten arbeiten.

Es gibt noch ein paar weitere Komponenten, die es wert sind, sich anzusehen, bevor wir uns dem Styling des Mars Theme widmen.

Die folgende MarsLink-Komponente stammt aus src/components/link.js, die ein Wrapper über der Komponente {@link Link} ist. Sie akzeptiert dieselben Eigenschaften wie die Komponente {@link Link}.

// src/components/link.js
import { connect, useConnect } from "frontity";
import Link from "@frontity/components/link";

const MarsLink = ({ children, ...props }) => {
  const { state, actions } = useConnect();

  /** A handler that closes the mobile menu when a link is clicked. */
  const onClick = () => {
    if (state.theme.isMobileMenuOpen) {
      actions.theme.closeMobileMenu();
    }
  };

  return (
    <Link {...props} onClick={onClick} className={className}>
      {children}
    </Link>
  );
};
// Connect the Item to gain access to `state` as a prop
export default connect(MarsLink, { injectProps: false });

Wie in diesem Tutorial erklärt, bietet die Link-Komponente ein link-Attribut, das eine Ziel-URL als Wert annimmt. Aus dem Dokument zitiert: es gibt ein <a>-Element in das resultierende HTML aus, erzwingt jedoch kein Neuladen der Seite, was passieren würde, wenn Sie einfach ein <a>-Element hinzufügen würden, anstatt die Link-Komponente zu verwenden.

Frontity Menü (src/components/nav.js)

Zuvor haben wir Werte für Menüelemente in der Datei frontity.settings.js definiert. In der Nav-Komponente (befindet sich in src/components/nav/js) werden diese Menüelementwerte durchlaufen, mit ihrer Seiten-url abgeglichen und die Komponente innerhalb der Header-Komponente angezeigt.

// src/components/nav.js
import { connect, styled } from "frontity";
import Link from "./link";

const Nav = ({ state }) => (
  <NavContainer>
    // Iterate over the menu exported from state.theme and menu items value set in frontity.setting.js
    {state.theme.menu.map(([name, link]) => {
      // Check if the link matched the current page url
      const isCurrentPage = state.router.link === link;
      return (
        <NavItem key={name}>
          {/* If link URL is the current page, add `aria-current` for a11y */}
          <Link link={link} aria-current={isCurrentPage ? "page" : undefined}>
            {name}
          </Link>
        </NavItem>
      );
    })}
  </NavContainer>
);
// Connect the Item to gain access to `state` as a prop
export default connect(Nav);

Das Mars Theme bietet zwei zusätzliche Menükomponenten – menu.js und menu-modal.js – für mobile Geräteansichten, die, wie nav.js, im Mars Theme GitHub-Repository verfügbar sind.

In Frontity werden Werte für Featured Media-Elemente in der theme.state.featured-Zeile der Root-Komponente definiert, die wir zuvor besprochen haben. Der vollständige Code ist in der Komponentendatei /src/components/featured-media.js verfügbar.

Nachdem wir nun mit dem Mars Theme sowie seinen Bausteinen, Komponenten und Funktionen vertraut sind, können wir uns den verschiedenen Ansätzen zuwenden, die für das Styling des Mars Theme-Frontends verfügbar sind.

Im weiteren Verlauf finden Sie dieses Frontity-Dokument möglicherweise als gute Referenz für die verschiedenen Styling-Ansätze, die wir behandeln.

Abschnitt 4: So stylen Sie ein Frontity-Projekt

Für diejenigen von uns, die von WordPress kommen, sieht das Styling in Frontity anders aus und fühlt sich anders an als die verschiedenen Ansätze zum Überschreiben von Stilen in einem typischen WordPress-Theme.

Zunächst bietet uns Frontity wiederverwendbare Komponenten, die mit styled-components erstellt wurden, und Emotion, eine CSS-Bibliothek zum Stylen von Komponenten in JavaScript, sofort einsatzbereit. Emotion ist bei React- und JavaScript-Entwicklern beliebt, aber in der WordPress-Community nicht so sehr, basierend auf dem, was ich gesehen habe. CSS-Tricks hat CSS-in-JS ausführlich behandelt, einschließlich wie es sich im Vergleich zu anderen Styling-Methoden verhält, und dieses Video bietet Hintergrundinformationen zur Bibliothek. Zu wissen, dass sowohl styled-components als auch Emotion verfügbar und einsatzbereit sind, ist also ein schöner Kontext für den Einstieg.

Die Dokumentation von Frontity bietet großartige Lernressourcen zum Stylen von Frontity-Komponenten sowie schrittweise Anleitungen zum Anpassen von Frontity-Theme-Stilen.

Ich bin neu in der Welt von CSS-in-JS, abgesehen von einigen allgemeinen Lektüren hier und da. Ich bin in einem Gatsby-Projekt mit CSS-in-JS-Styling in Berührung gekommen, aber Gatsby bietet eine Reihe anderer Styling-Optionen, die in Frontity oder dem Mars Theme nicht ohne weiteres verfügbar sind. Trotzdem habe ich das Gefühl, dass ich diesen Mangel an Erfahrung überwinden konnte, und das, was ich aus meiner Erkundungsarbeit gelernt habe, ist, wie ich die Dinge gestalten werde.

Vor diesem Hintergrund werden wir uns einige Styling-Beispiele ansehen und dabei auf die Frontity-Styling-Dokumentation verweisen, um uns mit noch mehr Informationen vertraut zu machen.

Verwenden von styled-components

Wie der Name schon sagt, benötigen wir eine Komponente, um sie zu stylen. Erstellen wir also zunächst eine styled-component mit Emotions styled-Funktion.

Angenommen, wir möchten eine wiederverwendbare <Button />-Komponente stylen, die in unserem Frontity-Projekt verwendet wird. Zuerst sollten wir eine <Button />-Komponente erstellen (wobei ihr div-Tag mit einem Punkt angehängt wird) und dann die Komponente mit einem Template-Literal für String-Stile aufrufen.

// Creating Button styled component
import { styled } from "frontity"

const Button = styled.div`
  background: lightblue;
  width: 100%;
  text-align: center;
  color: white;
`

Diese <Button />-Komponente kann nun in andere Komponenten importiert werden. Schauen wir uns speziell die Mars Theme <Header />-Komponente an, um zu sehen, wie die styled-component in der Praxis verwendet wird.

// mars-theme/src/components/header.js
import { connect, styled } from "frontity";
import Link from "./link";
import MobileMenu from "./menu";

const Header = ({ state }) => {
  return (
    <>
      <Container> // This component is defined later
        <StyledLink link="/"> // This component is defined later
          <Title>{state.frontity.title}</Title> // This component is defined later
        </StyledLink>
        // ...
      </Container>
    </>
  );
};

// Connect the Header component to get access to the `state` in its `props`
export default connect(Header);

// Defining the Container component that is a div with these styles
const Container = styled.div` 
  width: 848px;
  max-width: 100%;
  box-sizing: border-box;
  padding: 24px;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
`;
// Defining Title component that is h2 with these styles 
const Title = styled.h2`
  margin: 0;
  margin-bottom: 16px;
`;
// Defining StyledLink component that is a third-party Link component
const StyledLink = styled(Link)`
  text-decoration: none;
`;

Im obigen Codebeispiel wird die Komponente <StyledLink /> (Zeilen 39-41, oben hervorgehoben) verwendet, um eine andere Komponente, <Link />, zu stylen. Ebenso werden die styled-components <Container /> und <Title /> verwendet, um den Sitetitel und die Breite des Hauptcontainers der Site zu stylen.

In den Emotion-Dokumenten wird beschrieben, wie eine gestylte Komponente verwendet werden kann, solange sie className-Eigenschaften akzeptiert. Dies ist ein nützliches Styling-Tool, das mithilfe einer Variablen erweitert werden kann, wie im folgenden Beispiel aus der Frontity-Dokumentation gezeigt:

// mars-theme/src/components/header.js 
// ...
// We create a variable to use later as an example
Const LinkColor = "green";

// ... 

// Defining StyledLink component that is a third-party Link component
const StyledLink = styled(Link)`
  text-decoration: none;
  Background-color: ${linkColor};
`;

Die obige styled-Komponente wird im Mars Theme ausgiebig verwendet. Aber bevor wir fortfahren, schauen wir uns an, wie man eine CSS-Eigenschaft zum Stylen von Komponenten verwendet.

Verwenden einer CSS-Eigenschaft

Die css-Eigenschaft ist als Template-Literal für Inline-Styling aus dem Frontity-Kernpaket verfügbar. Sie ist ähnlich wie styled-components, außer dass css keine React-Komponente zurückgibt, sondern ein spezielles Objekt, das über die css-Eigenschaft an eine Komponente übergeben werden kann.

/* Using as CSS prop */
import { css } from "frontity";

const PinkButton = () => (
  <div css={css`background: pink`}>
    My Pink Button
  </div>
);

Sehen Sie? Wir können eine Komponente inline mit der css-Eigenschaft einer Komponente stylen. Zusätzliche Anwendungsbeispiele sind in den Emotion-Dokumenten verfügbar.

Verwenden der <Global />-Komponente

<Global /> ist eine React-Komponente, die es uns ermöglicht, site-weite allgemeine Stile zu erstellen, obwohl Frontity sie nicht für die Leistung optimiert. Globale Stile sollten der Root-Komponente <Theme /> hinzugefügt werden.

// packages/mars-theme/src/components/index.js
// ...

import { Global, css, styled } from "frontity";
import Title from "./title";
import Header from "./header";
// ...

// Theme root
const Theme = ({ state }) => {
  // Get information about the current URL.
  const data = state.source.get(state.router.link);

  return (
   <>
     {/* Add some metatags to the <head> of the HTML. */}
      <Title />
        // ...
      {/* Add global styles */}
      <Global styles={globalStyles} />
      {/* Add the header of the site. */}
      <HeadContainer>
        <Header />
      </HeadContainer>
        // ...
   </>
  );
 };

export default connect(Theme);

const globalStyles = css`
  body {
    margin: 0;
    font-family: -apple-system, "Helvetica Neue", Helvetica, sans-serif;
  }
  a,
  a:visited {
    color: inherit;
    text-decoration: none;
  }
`;

const HeadContainer = styled.div`
  // ...
`;

Die <Global />-Komponente verfügt über ein style-Attribut, das eine css-Funktion als Wert annimmt und aus Standard-CSS innerhalb von Backticks (Zeilen 35-45, oben hervorgehoben) als Template-Literale besteht. Frontity empfiehlt die Verwendung globaler Stile für global verwendete HTML-Tags wie <html>, <body>, <a> und <img>.

Zusätzliche CSS-Styling-Optionen – einschließlich einer dynamischen CSS-Eigenschaft und React-Style-Eigenschaften – werden in dieser Frontity-Anleitung zum Styling beschrieben.

Ressourcen zum Anpassen eines Frontity-Themes

Ich habe viel recherchiert, bevor ich mein Mars Theme-Projekt startete, und dachte, ich würde einige der nützlicheren Ressourcen teilen, die ich zum Stylen von Frontity-Themes gefunden habe:

  • Offizielle Frontity-Themes. Zusätzlich zum Standard-Mars Theme hat Frontity ein gebrauchsfertiges Paket, das das Standard-WordPress Twenty Twenty Theme vollständig in ein Frontity-Projekt portiert. Sie werden im nächsten Abschnitt feststellen, dass meine Stilanpassungen von dieser großartigen Lernressource inspiriert wurden.
  • Community-Themes. Zum Zeitpunkt des Verfassens dieses Artikels gibt es insgesamt neun Frontity-Community-Mitglieder, die voll funktionsfähige Theme-Pakete beigesteuert haben. Diese Themes können in Ihr eigenes Projekt geklont und an Ihre Bedürfnisse angepasst werden. Ebenso verfügen viele der auf der Frontity-Showcase-Seite enthaltenen Sites über GitHub-Repository-Links, und so wie wir Design-Tipps von WordPress-Themes kopieren oder übernehmen können, können wir diese Ressourcen verwenden, um unser eigenes Frontity-Theme anzupassen, indem wir auf diese Pakete verweisen.
  • Erstellen Sie Ihr eigenes Theme von Grund auf neu. Die Frontity-Tutorial-Site enthält eine ausgezeichnete Schritt-für-Schritt-Anleitung zum Erstellen eines eigenen voll funktionsfähigen Theme-Pakets von Grund auf. Obwohl es etwas zeitaufwändig ist, alles durchzugehen, ist es der beste Ansatz, um ein Frontity-Site-Projekt vollständig zu verstehen.

Nachdem wir nun die gängigeren Frontity-Styling-Techniken behandelt haben, wenden wir das Gelernte an, um mit der Anpassung unseres Mars Theme-Projekts zu beginnen.

Abschnitt 5: Anpassen des Frontity Mars Theme

Ich werde eines meiner funktionierenden Frontity-Projekte teilen, bei dem ich das Mars Theme als Basis genommen und es mit den Ressourcen, die wir bisher behandelt haben, modifiziert habe. Da dies mein Lernspielplatz ist, habe ich mir die Zeit genommen, von Frontity-Standard-Themes, Community-Themes und Frontity-Showcase-Sites zu lernen.

Hier sind Beispiele, wie ich das Mars Theme von Frontity für mein Headless WordPress-Site-Projekt angepasst habe.

Ändern des Theme-Paketnamens

Zuerst wollte ich den Paketnamen @frontity/mars-theme in etwas anderes ändern. Es ist eine gute Idee, den Paketnamen zu ändern und sicherzustellen, dass alle Abhängigkeiten in der Paketdatei auf dem neuesten Stand sind. Luis Herrera beschreibt die erforderlichen Schritte zum Umbenennen des Mars Theme-Pakets in diesem Frontity Community Forum, das ich als Referenz verwendet habe, um vom Paket @fontity/mars-theme zu @frontity/labre-theme zu wechseln.

Öffnen Sie also die Datei package.json und ändern Sie die Eigenschaft name in Zeile 2. Dies ist der Name des Pakets, das im gesamten Projekt verwendet wird.

Screenshot of the package.json file open in VS Code. The left panel shows the files and the right panel displays the code.
Ich habe mein Projekt in meiner Datei package.json von mars-theme in labre-theme umbenannt.

Wir sollten bei dieser Gelegenheit auch den Namen des Projektordners aktualisieren. Das können wir in Zeile 25 tun. Ich habe meinen von ./package/mars-theme in ./package/labre-theme geändert. Nun ist das Theme-Paket korrekt als Abhängigkeit aufgeführt und wird in das Projekt importiert.

Unsere Datei frontity-settings.js muss die Namensänderung widerspiegeln. Öffnen wir sie also und

  • benennen Sie den Paketnamen in Zeile 13 um (ich habe meinen von @frontity/mars-theme in @frontity/labre-theme geändert), und
  • benennen Sie den Namen in Zeile 3 um (ich habe meinen von mars-demo in labre-demo geändert).
// @frontity-settings.js
const settings = {
  "name": "labre-demo",
  "state": {
    "frontity": {
      "url": "http://frontitytest.local",
      "title": "Frontity Demo Blog",
      "description": "Exploring Frontity as Headless WordPress"
    }
  },
  "packages": [
    {
      "name": "@frontity/labre-theme",
      "state": {
        "theme": {
          "menu": [
            ["Home", "/"],
            ["Block", "/category/block/"],
            ["Classic", "/category/classic/"],
            ["Alignments", "/tag/alignment-2/"],
            ["About", "/about/"]
          ],
 // ...

Als Nächstes möchten wir das Projekt mit diesen Änderungen neu initialisieren. Wir sollten den Ordner node_modules mit rm -rf node_modules in einem Terminal löschen und das npm-Paket mit yarn install neu installieren. Sobald das npm-Paket neu installiert ist, wird alles intern ordnungsgemäß verknüpft und unser Frontity-Projekt läuft ohne Fehler.

Refactoring der Navigation mit dynamischem Menüabruf

Wie wir bereits besprochen haben, sind Frontity-Menüelemente entweder in der Datei frontity.setting.js oder in der Komponente index.js fest codiert, die im Frontity-state gespeichert ist. WordPress kann das Frontity-Menü jedoch dynamisch abrufen. Tatsächlich hat Frontity zufällig ein YouTube-Video zu diesem Thema. Lassen Sie mich die wichtigsten Schritte hier aufschlüsseln.

Der erste Schritt ist die Installation des Plugins WP-REST-API V2 Menus in WordPress. Das Plugin ist im WordPress Plugin Directory frei verfügbar, was bedeutet, dass Sie es direkt aus dem WordPress-Admin finden und aktivieren können.

Warum brauchen wir dieses Plugin? Es erweitert die neuen Routen auf alle registrierten WordPress-Menüs um die REST API (z. B. /menus/v1/menus/<slug>).

Wenn wir unsere Projektseite unter /wp-json/menu/v1/menus überprüfen, sollte sie unsere ausgewählten Menüelemente im JSON anzeigen. Wir können die Menüelemente mit der slug-Eigenschaft des Menüelements abrufen.

Als Nächstes verwenden wir die Funktion menuHandler aus dem Tutorial. Erstellen Sie eine neue Datei menu-handler.js unter src/components/handler/menu-handler.js und fügen Sie den folgenden Code ein:

// src/components/handler/menu-handler.js
const menuHandler = {
  name: "menus",
  priority: 10,
  pattern: "/menu/:slug",
  func: async ({ link, params, state, libraries }) => {
    console.log("PARAMS:", params);
    const { slug } = params;

    // Fetch the menu data from the endpoint
    const response = await libraries.source.api.get({
      endpoint: `/menus/v1/menus/${slug}`,
    });

    // Parse the JSON to get the object
    const menuData = await response.json();

    // Add the menu items to source.data
    const menu = state.source.data[link];
    console.log(link);
    Object.assign(menu, {
      items: menuData.items,
      isMenu: true,
    });
  },
};

export default menuHandler;

Diese Funktion menuHandler wird nur ausgeführt, wenn der Wert pattern (d. h. /menu/:slug) übereinstimmt. Aktualisieren wir nun unsere Root-Komponente /src/index.js, damit sie den Handler importiert:

// src/index.js
import Theme from "./components";
import image from "@frontity/html2react/processors/image";
import iframe from "@frontity/html2react/processors/iframe";
import link from "@frontity/html2react/processors/link";
import menuHandler from "./components/handlers/menu-handler";

const labreTheme = {
  // ...
  state: {
    theme: {
      autoPrefetch: "in-view",
      menu: [],
      {/* Add menuURL property with menu slug as its value */}
      menuUrl: "primary-menu",
      isMobileMenuOpen: false,
      // ...
    },
  },

  /** Actions are functions that modify the state or deal with other parts of
    * Frontity-like libraries */
  actions: {
    theme: {
      toggleMobileMenu: ({ state }) => {
        state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;
      },
      closeMobileMenu: ({ state }) => {
        state.theme.isMobileMenuOpen = false;
      },
      {/* Added before SSR action */}
      beforeSSR: async ({ state, actions }) => {
        await actions.source.fetch(`/menu/${state.theme.menuUrl}/`);
      },
    },
  },
  libraries: {
    // ...
    {/* Added menuHandler source */}
    source: {
      handlers: [menuHandler],
    },
  },
};

export default labreTheme;

Fügen Sie ein Array von Handlern unter der Eigenschaft source hinzu und rufen Sie Daten vor der Funktion beforeSSR ab. Es werden keine Daten abgerufen, aber der menu-handler-Slug wird abgeglichen, was bedeutet, dass menuHandler() ausgeführt wird. Dadurch werden die Menüelemente in den State eingefügt und stehen zur Bearbeitung zur Verfügung.

Bitte beachten Sie, dass wir hier eine neue Eigenschaft menuUrl (Zeile 15 oben) hinzugefügt haben, die als Variable an unserem Endpunkt in Handlern sowie in der Komponente nav.js verwendet werden kann. Wenn wir dann den Wert von menuUrl in der Root-Komponente index.js ändern, könnten wir ein anderes Menü anzeigen.

Führen wir diese Daten über den State in unser Theme ein und ordnen sie mit menu-items zu, um sie auf der Site anzuzeigen.

// src/components/nav.js
import { connect, styled } from "frontity";
import Link from "./link";

/** Navigation Component. It renders the navigation links */
const Nav = ({ state }) => {
  {/* Define menu-items constants here */}
  const items = state.source.get(`/menu/${state.theme.menuUrl}/`).items;

  return (
  <NavContainer>
    {items.map((item) => {
       return (
        <NavItem key={item.ID}>
           <Link link={item.url}>{item.title}</Link>
         </NavItem>
      );
    })}
  </NavContainer>
  );
};

export default connect(Nav);

const NavContainer = styled.nav`
  list-style: none;
  // ...

Wenn wir unseren menu-Slug hier und in index.js ändern, erhalten wir ein anderes Menü. Um dynamische Menüelemente in der mobilen Ansicht anzuzeigen, sollten wir die Komponenten menu-modal.js ebenfalls aktualisieren.

Zusätzlich beschreibt das Tutorial, wie man auch verschachtelte Menüs abruft, was Sie im Tutorial-Video ab etwa 18:09 lernen können.

Modifizieren der Dateistruktur

Ich habe beschlossen, meinen Labre (früher bekannt als Mars) Theme-Ordner neu zu strukturieren. So sieht es nach den Änderungen aus:

#! modified Frontity labre-theme structure
packages/labre-theme/
|__ src/
  |__ index.js
  |__ components/
     |__image/
     |__assets/
     |__ list/
     |__ footer/
       |__footer.js
       |__ widget.js
     |__ header/
       |__ header.js
       |__ menu-icon.js
       |__ menu-model.js
       |__ nav.js
     |__ pages/
       |__ index.js
       |__ page.js
     |__ posts/
       |__ index.js
       |__ post.js
     |__ styles/
     // ...

Wie Sie sehen können, habe ich separate Ordner für Seiten, Stile, Header, Beiträge und Bilder hinzugefügt. Bitte beachten Sie, dass wir die Dateipfade in index.js und anderen verwandten Komponenten aktualisieren müssen, wenn wir die Organisation von Dateien und Ordnern ändern. Andernfalls zeigen sie ins Leere!

Möglicherweise haben Sie bemerkt, dass die ursprüngliche Mars Theme-Ordnerstruktur weder eine Footer-Komponente noch eine separate Seitenkomponente enthält. Erstellen wir diese Komponenten, um zu demonstrieren, wie unsere neue Ordnerstruktur funktioniert.

Wir können mit der Seitenkomponente beginnen. Das Mars Theme generiert standardmäßig sowohl Seiten als auch Beiträge mit der Komponente posts.js – das liegt daran, dass Seiten und Beiträge im Wesentlichen gleich sind, außer dass Beiträge Metadaten haben (z. B. Autoren, Datum usw.) und sie damit davonkommen können. Aber wir können sie für unsere eigenen Bedürfnisse trennen, indem wir den Code in posts.js kopieren und in eine neue Datei pages.js in unserem Ordner /pages einfügen.

// src/components/pages/page.js
import React, { useEffect } from "react";
import { connect, styled } from "frontity";
import List from "../list";

const Page = ({ state, actions, libraries }) => {
  // Get information about the current URL.
  const data = state.source.get(state.router.link);
  // Get the data of the post.
  const page = state.source[data.type][data.id];
  //  ...
  // Load the page, but only if the data is ready.
  return data.isReady ? (
    <Container>
      <div className="post-title">
        <Title dangerouslySetInnerHTML={{ __html: page.title.rendered }} />
      </div>

      {/* Render the content using the Html2React component so the HTML is processed by the processors we included in the libraries.html2react.processors array. */}
      <Content>
        <Html2React html={page.content.rendered} />
      </Content>
    </Container>
  ) : null;
};
// Connect the Page component to get access to the `state` in its `props`
export default connect(Page);

// Copy styled components from post.js except, DateWrapper
const Container = styled.div`
    width: 90vw;
    width: clamp(16rem, 93vw, 58rem);
    margin: 0;
    padding: 24px;
`
// ..

Wir haben hier lediglich die Metadaten aus post.js (Zeilen 31-34 und 55-76) und die entsprechenden styled-Komponenten entfernt. Genau wie beim Mars Theme /list-Ordner sollten wir die loadable-Funktion sowohl in den Seiten- als auch in den Beitragsordnern exportieren, um die <List />-Komponente per Code-Splitting aufzuteilen. Auf diese Weise wird die <List />-Komponente nicht angezeigt, wenn sich ein Benutzer auf einem einzelnen Beitrag befindet.

// src/components/pages/index.js
import { loadable } from "frontity";

/** Codesplit the list component so it's not included
*   if the users load a post directly. */
export default loadable(() => import("./page"));

Als Nächstes sollten wir den Pfad-URL der Komponente /src/components/index.js wie unten gezeigt aktualisieren:

// src/components/index.js
import { Global, css, connect, styled, Head } from "frontity";
import Switch from "@frontity/components/switch";
import Header from "./header/header";
import List from "./list";
import Page from "./pages/page";
import Post from "./posts/post";
import Loading from "./loading";
import Title from "./title";
import PageError from "./page-error";

/** Theme is the root React component of our theme. The one we will export
 * in roots. */
const Theme = ({ state }) => {
  // Get information about the current URL.
  const data = state.source.get(state.router.link);

  return (
    <>
      // ...

      {/* Add some global styles for the whole site */}
       <Global styles={globalStyles} />
      {/* Add the header of the site. */}
      <HeadContainer>
        <Header />
      </HeadContainer>
      {/* Add the main section */}
      <Main>
        <Switch>
          <Loading when={data.isFetching} />
          <List when={data.isArchive} />
          <Page when={data.isPage} /> {/* Added Page component */}
          <Post when={data.isPostType} />
          <PageError when={data.isError} />
        </Switch>
      </Main>
    </>
  );
};

export default connect(Theme);

// styled components

Jetzt importieren wir die Komponente <Page / und haben unsere styled-component <Main /> hinzugefügt.

Kommen wir nun zu unserer benutzerdefinierten Footer-Komponente. Sie wissen wahrscheinlich, was zu tun ist: Erstellen Sie eine neue Komponentendatei footer.js und legen Sie sie in den Ordner /src/components/footer/. Wir können unserem Footer einige Widgets hinzufügen, die die Sitemap und eine Art „Powered by“-Text anzeigen:

// src/components/footer/footer.js
import React from "react";
import { connect, styled } from "frontity";
import Widget from "./widget"

const Footer = () => {
  return (
  <>
    <Widget />
    <footer>
      <SiteInfo>
        Frontity LABRE Theme 2021 | {" "} Proudly Powered by {"  "}
        <FooterLinks href="https://wordpress.org/" target="_blank" rel="noopener">WordPress</FooterLinks>
        {"  "} and
        <FooterLinks href="https://frontity.org/" target="_blank" rel="noopener"> Frontity</FooterLinks>
      </SiteInfo>
    </footer>
    </>
  );
};

export default connect(Footer);
// ...

Dies ist ein super einfaches Beispiel. Bitte beachten Sie, dass ich eine <Widget />-Komponente importiert (Zeile 4, oben hervorgehoben) und die Komponente aufgerufen habe (Zeile 9, oben hervorgehoben). Wir haben noch keine <Widget />-Komponente, also erstellen wir sie gleich mit. Das kann eine Datei widget.js im selben Verzeichnis wie der Footer sein, /src/components/footer/.

Screen shot of VS code editor open to a widget.js file that shows the syntax highlighted markup for a component.
Diese widget.js Komponente wurde inspiriert von Aamodt GroupFooter-Komponente, die in einem GitHub-Repository verfügbar ist.
Four columns of links, each with a heading. The text is dark against a light gray background.
Das Widget ist fest codiert, funktioniert aber.

Anpassen des Theme-Headers

Die Standardkomponente header.js im Mars Theme ist sehr einfach gehalten, mit einem Sitetitel und einer Site-Beschreibung sowie Navigationselementen darunter. Ich wollte die Header-Komponente mit einem Site-Logo und -Titel links und der Komponente nav.js (Top-Navigation) rechts umgestalten.

// src/components/header.js
import { connect, styled } from "frontity";
import Link from "./link";
import Nav from "./nav";
import MobileMenu from "./menu";
import logo from "./images/frontity.png"

const Header = ({ state }) => {
  return (
    <>
      <Container>
        <StyledLink link="/">
         {/* Add header logo*/}
          <Logo src={logo} />
          <Title>{state.frontity.title}</Title>
        </StyledLink>
          {/*<Description>{state.frontity.description}</Description> */}
          <Nav />
      </Container>
        <MobileMenu />
    </>
  );
};
// Connect the Header component to get access to the `state` in its `props`
export default connect(Header);

const Container = styled.div`
  width: 1000px;
  // ...
  `}
{/* Logo styled component */}
const Logo = styled.img`
  max-width: 30px;
  display: inline-block;
  border-radius: 15px;
  margin-right: 15px;
`;

// ...

Meine umgestaltete Komponente header.js importiert ein Logo-Bild (Zeile 6, oben hervorgehoben) und verwendet es in Zeile 14. Die unten gezeigte Komponente nav.js ist im Grunde dieselbe, nur mit einigen geringfügigen Styling-Modifikationen.

Screenshot showing refactored site header with site logo and site title (left) and top navigation (right)

Hinzufügen der <Global>-Style-Komponente

Wir haben die Komponente <Global> und ihre Verwendung für site-weites CSS bereits behandelt. Es gibt nur wenige globale Stile in der Standard-Mars Theme-Root-Komponente, und ich wollte weitere hinzufügen.

Ich habe das mit einer separaten Datei globalStyles unter /src/components/styles/globalStyles.js gemacht – ähnlich wie beim Frontity Twenty Twenty Theme – und Root-Variablen, ein CSS-Reset und gängige site-weite Elementstile hinzugefügt, die im GitHub-Repo zu finden sind.

Implementieren von fließender Typografie

Obwohl es nicht wirklich in den Rahmen fällt, wollte ich unbedingt fließende Typografie in meinem benutzerdefinierten Theme verwenden, als Teil meiner gesamten Lernreise. Also habe ich es zu den globalen Stilen hinzugefügt.

CSS-Tricks hat fließende Typografie und wie die clamp()-Funktion verwendet wird, um Zielfontgrößen festzulegen, ausführlich behandelt. Basierend auf diesen CSS-Tricks-Beiträgen und diesem Picalilli-Beitrag habe ich zwei benutzerdefinierte Eigenschaften mit geklammerten Schriftgrößenbereichen für das Element :root in der Komponente globalStyles.js definiert.

// src/components/styles/globalStyles.js
:root {
  --wide-container: clamp(16rem, 90vw, 70rem);
  --normal-container: clamp(16rem, 90vw, 58rem);
}

Der wide-container-Wrapper wird für Header- und Footer-Komponenten verwendet, während der normal-container für die Anzeige von Beiträgen und Seiten verwendet wird.

Ich habe auch die Überschriften unter elementBase in der Komponente globalStyles.js wie in diesem GitHub-Repo gezeigt geklammert.

Die Arbeit mit der Funktion clamp() hat Spaß gemacht, weil ich dadurch einen Größenbereich ohne jegliche Medienabfragen festlegen konnte!

Hinzufügen von Webfonts zum Theme

Ich wollte auch eine andere Webfont in meinem Theme verwenden. Das Importieren von Webfonts in CSS mithilfe von @font-face wird hier auf CSS-Tricks behandelt. Frontitys Twenty Twenty Theme verwendet es, also ist das auch ein guter Ort zum Nachschlagen.

Ich wollte drei Google-Schriftarten:

Wir können die Schriftarten entweder mit einem <link> im HTML-Head oder mit @import in CSS verwenden. Aber Chris hat erklärt, wie man @font-face mit Google Fonts verwendet, wodurch wir die Anzahl der HTTP-Anfragen optimieren können, da wir die Schriftarten auf unseren eigenen Server herunterladen können.

Ich verwende den Google Webfonts Helper, um die heruntergeladenen Schriftartdateien zu hosten. Hier ist, was ich bekommen habe:

/* source: google webfonts helper */
/* source-sans-pro-regular - latin */
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: url('../fonts/source-sans-pro-v14-latin-regular.eot'); /* IE9 Compat Modes */
  src: local(''),
    url('../fonts/source-sans-pro-v14-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
    url('../fonts/source-sans-pro-v14-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
    url('../fonts/source-sans-pro-v14-latin-regular.woff') format('woff'), /* Modern Browsers */
    url('../fonts/source-sans-pro-v14-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
    url('../fonts/source-sans-pro-v14-latin-regular.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}

Wenn ich mir das Twenty Twenty Theme als Referenz ansehe, wie es dort gemacht wird, habe ich eine Datei font-face.js erstellt und sie in den Ordner /src/components/styles eingefügt, wie in diesem GitHub-Repository gezeigt.

Diese Schriftarten verweisen auf einen Ordner /fonts, der nicht existiert. Erstellen wir also dort einen und stellen wir sicher, dass sich alle richtigen Schriftartdateien darin befinden, damit die Schriftarten ordnungsgemäß geladen werden.

Importieren von globalStyles und @face-font Komponenten in die Root-Komponente <Theme />

Öffnen wir unsere Theme-Root-Komponente, /src/components.index.js, und fügen wir unsere Komponenten globalStyles.js und font-face.js dort hinzu. Wie unten gezeigt, sollten wir beide Komponenten in index.js importieren und die Komponenten später aufrufen.

// src/components/index.js

// ...
import FontFace from "./styles/font-face";
import globalStyles from "./styles/globalStyles";

/** Theme is the root React component of our theme. The one we will export
 * in roots. */
const Theme = ({ state }) => {
  // Get information about the current URL.
  const data = state.source.get(state.router.link);

  return (
    <>
    // ...

    {/* Add some global styles for the whole site, like body or a's.
     *  Not classes here because we use CSS-in-JS. Only global HTML tags. */}
      <Global styles={globalStyles} />
      <FontFace />
      {/* Add the header of the site. */}
      // ...

export default connect(Theme);

 {/* delete original globalStyles css component */}

 // ...

Schließlich sollten wir die Mars Theme globalStyles-Komponente aus index.js entfernen. Jetzt werden unsere neuen Schriftarten im gesamten Projekt angewendet.

Stylen von Seiten und Beiträgen

Unsere Beiträge und Seiten sind so gut wie gestylt, abgesehen von einigen Gutenberg-Blockinhalten, wie Schaltflächen, Zitaten usw.

Um unsere Beitrags-Metadaten zu stylen, fügen wir Symbole für Autor, Datum, Kategorien und Tags hinzu. Frontitys Port des WordPress Twenty Nineteen Themes verwendet SVG-Symbole und Komponenten für die Komponenten author.js, categories.js, posted-on.js und tags.js, die wir vollständig kopieren und in unserem eigenen Projekt verwenden können. Ich habe buchstäblich den übergeordneten Ordner entry-meta und alles darin aus dem Theme frontity-twentynineteen kopiert und alles in den Projektordner /components/posts/ eingefügt.

Als Nächstes sollten wir unsere Komponente src/components/list/list-item.js aktualisieren, damit wir die neuen Assets verwenden können:

// src/components/list/list-item.js

import { connect, styled } from "frontity";
import Link from "../link";
import FeaturedMedia from "../featured-media";

// import entry-meta
import Author from "../entry-meta/author";
import PostedOn from "../entry-meta/posted-on";

const Item = ({ state, item }) => {

  return (
    <article>
      <div>
        {/* If the post has an author, we render a clickable author text. */}
        <EntryMeta>
          <Author authorId={item.author} /> {"|  "}
          <PostedOn post={item} />
        </EntryMeta>
      </div>

      <Link link={item.link}>
        <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} />
      </Link>
      // ...
    </article>
  );
};

// Connect the Item to gain access to `state` as a prop
export default connect(Item);

Die styled-component für die Komponente <EntryMeta /> kann wie im GitHub-Repository gezeigt aussehen.

Mit diesen Stilen sieht unsere Archivseite-Metadaten gut aus, mit Symbolen, die vor der Taxonomie der Metadaten angezeigt werden (Autoren, Veröffentlichungsdatum).

Hier werden wir das Styling der Archivtaxonomieseite mit einem beschreibenderen Header modifizieren. Aktualisieren wir die Komponente list.js in unserem /src/components/list/list.js wie unten gezeigt.

// src/components/list/list.js

import React from "react";
import { connect, styled, decode } from "frontity";
import Item from "./list-item";
import Pagination from "./pagination";

const List = ({ state }) => {
  // Get the data of the current list.
  const data = state.source.get(state.router.link);

  return (
    <Container className="entry-content">
      {/* If the list is a taxonomy, we render a title. */}
      {data.isAuthor ? (
        <Header>
          Author Archives:{" "}
          <PageDescription>
          {decode(state.source.author[data.id].name)}
          </PageDescription>
        </Header>
        ) : null}

        {/* If the list is a taxonomy or category, we render a title. */}
        {data.isTaxonomy || data.isCategory ? (
          <Header>
            {data.taxonomy.charAt(0).toUpperCase() + data.taxonomy.slice(1)}{" "}
            Archives:{" "}
            <PageDescription>
            {decode(state.source[data.taxonomy][data.id].name)}
            </PageDescription>
          </Header>
        ) : null}
      // ...

      <Pagination />
    </Container>
  );
};
export default connect(List);

const PageDescription = styled.span`
  font-weight: bold;
  font-family: var(--body-family);
    color: var(--color-text);
`;
// ...

Im obigen Beispiel haben wir taxonomy.id data mit der styled-component PageDesctiption umschlossen und einige Styling-Regeln angewendet.

Die Beitrags-Paginierung im Standard-Mars Theme ist sehr einfach und hat fast kein Styling. Leihen wir uns wieder vom Frontity Twenty Nineteen Theme und fügen wir die Paginierungskomponente und das Styling aus dem Theme hinzu, indem wir die Komponentendatei pagination.js vollständig kopieren und in /src/components/list/pagination.js in unserem Theme einfügen.

Showing two example posts in a post list, one with comments enabled, and the other with comments disabled. The post content is black against a light gray background.
Ich habe einige geringfügige CSS-Anpassungen hinzugefügt, und es funktioniert perfekt in unserem Projekt.

Um die eigentlichen einzelnen Beiträge und Seiten anzupassen, erstellen wir einen fetten Header-Titel, der zentriert ist und die Metadaten anzeigt:

// src/components/posts/post.js

// ...
// Import entry-meta
import Author from "../entry-meta/author";
import PostedOn from "../entry-meta/posted-on";
import Categories from "../entry-meta/categories";
import Tags from "../entry-meta/tags";

const Post = ({ state, actions, libraries }) => {
  // ...
  // Load the post, but only if the data is ready.
  return data.isReady ? (
    <Container className="main">
      <div>
        <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} />

        {/* Hide author and date on pages */}
        {data.isPost && (
          <EntryMeta>
          <Author authorId={post.author} />
          <PostedOn post={post} />
        </EntryMeta>
        )}
      </div>

      {/* Look at the settings to see if we should include the featured image */}
      {state.theme.featured.showOnPost && (
        <FeaturedMedia id={post.featured_media} />
      )}

      {data.isAttachment ? (
        <div dangerouslySetInnerHTML={{ __html: post.description.rendered }} />
      ) : (
        <Content>
          <Html2React html={post.content.rendered} />
          {/* Add footer meta-entry */}
          <EntryFooter>
            <Categories cats={post.categories} />
            <Tags tags={post.tags} />
          </EntryFooter>
        </Content>
      )}
    </Container>
  ) : null;
};

export default connect(Post);
// ...
Screenshot showing header meta-entry (top) and footer meta-entry (bottom). Header has a large bold title above meta containing the author name and post date, all centered. There is a thick line between the header and content. The content is a simple paragraph containing lorem ipsum text. Dark content against a light gray background.

Hinzufügen von Gutenberg-Blockstilen

WordPress verwendet ein separates Stylesheet für Blöcke im Block-Editor. Im Moment wird dieses Stylesheet nicht verwendet, aber es wäre großartig, wenn wir einige Basisstile dort einfügen könnten, die wir für die verschiedenen Blockinhalte verwenden, die wir zu Seiten und Beiträgen hinzufügen.

A post with DevTools open and highlighting the markup for the button component.
Die Klasse .wp-block-buttons ist im WordPress-Block-Stylesheet deklariert, das wir (noch) nicht verwenden.

Der WordPress Block-Editor verwendet zwei Styling-Dateien: style.css und theme.css. Kopieren wir diese direkt aus Frontitys Port des Twenty Twenty Themes, da sie dort die WordPress-Stile implementiert haben. Wir können diese in einem Ordner /styles/gutenberg/ ablegen.

„Gutenberg“ war der Codename, der dem WordPress Block-Editor während seiner Entwicklung gegeben wurde. Er wird manchmal immer noch so genannt.

Fügen wir die beiden oben genannten Stildateien zu unserer Theme-Root-Komponente /src/components/index.js hinzu, genau wie wir es zuvor für globalStyles getan haben:

//  src/components/index.js
import gutenbergStyle from "./styles/gutenberg/style.css";
import gutenbergTheme from "./styles/gutenberg/theme.css"

Hier ist unsere aktualisierte Root-Komponente <Theme />:

// src/components/index.js

// ...
import FontFace from "./styles/font-face";
import globalStyles from "./styles/globalStyles";
// Add Gutenberg styles
import gutenbergStyle from "./styles/gutenberg/style.css";
import gutenbergTheme from "./styles/gutenberg/theme.css"

/** Theme is the root React component of our theme. The one we will export
  * in roots. */
const Theme = ({ state }) => {
  // Get information about the current URL.
  const data = state.source.get(state.router.link);

  return (
    <>
    // ...
    {/* Add some global styles for the whole site, like body or a's.
      * Not classes here because we use CSS-in-JS. Only global HTML tags. */}
      <Global styles={globalStyles} />
      <Global styles={css(gutenbergStyle)} />
      <Global styles={css(gutenbergTheme)} />
      <FontFace />
      {/* Add the header of the site. */}
      // ...
export default connect(Theme);

 {/* Delete original globalStyles css component */}
 // ...

Wir könnten Stile auf viele verschiedene Arten überschreiben. Ich habe mich für einen einfachen Weg entschieden. Zum Beispiel das Überschreiben von Schaltflächenstilen – .wp-block-buttons – in der styled-component für Seiten und Beiträge.

Screenshot showing button style customization (left panel) and styled button in blue (right)

Wir können alle anderen Blockstile auf dieselbe Weise überschreiben. Im Frontity Twenty Nineteen Theme wird das gesamte Stylesheet aus der WordPress-Version des Themes hinzugefügt, um genau dasselbe Erscheinungsbild zu reproduzieren. Frontitys Twenty Twenty Port verwendet nur ausgewählte Stile aus den WordPress Twenty Twenty Themes, allerdings als Inline-Stile.

Zusätzliche Styling-Ressourcen

Alle Ressourcen, die wir in diesem Abschnitt zum Styling behandelt haben, sind im GitHub-Repository verfügbar. Wenn Sie mein Projekt @frontity/labre-theme weiter ausbauen möchten, finden Sie hier die Ressourcen, die ich gesammelt habe.

Abschnitt 6: Ressourcen und Danksagung

Es gibt zahlreiche Ressourcen, um Ihr Frontity-Projekt zu lernen und anzupassen. Bei der Erstellung dieses Beitrags habe ich mich ausführlich auf die folgenden Ressourcen bezogen. Bitte beachten Sie die Originalbeiträge für detailliertere Informationen.

Frontity Dokumentation und Artikel

  • Schritt-für-Schritt-Tutorial (Frontity): Dies ist der perfekte Ausgangspunkt, wenn Sie neu bei Frontity sind, oder auch wenn Sie Frontity bereits verwendet haben und sich verbessern möchten.
  • Konzeptionelle Anleitungen (Frontity): Diese Anleitungen helfen bei der Lösung einiger der häufigsten Herausforderungen, die bei der Arbeit mit dynamischem serverseitigem Rendering in React-Apps in Verbindung mit WordPress auftreten.
  • Frontity API-Referenz (Frontity). Diese enthält detaillierte Informationen über Frontity CLI, Pakete, Plugins und Themes. Sobald Sie die Grundlagen der Arbeit mit Frontity beherrschen, werden Sie wahrscheinlich die meiste Zeit hier verbringen, wenn Sie an Projekten arbeiten.“
  • Frontity Beispiel-Repo (Frontity): Dies ist eine Sammlung von Frontity-Projekten, die demonstrieren, wie Frontity in der Praxis verwendet wird.

Frontity case studies

Frontity talks and videos

Frontity community

Frontity has a vibrant and engaging community forum for asking questions or getting help regarding your Frontity project.

Wrapping up and personal thoughts

If you can’t already tell from this post or the others I’ve written, I have a huge passion for headless WordPress sites. As I wrote in a previous article, I came across Frontity through when Chris posted this article. I have been experimenting with it for over six months, choosing to take a deep drive into Frontity and the building blocks used in its default Mars Theme. I must admit that it’s a fascinating software framework and I’ve had an enjoyable learning experience. I may even use this sort of setup for my own personal site!

Here are a few key takeaways from my experience working with Frontity so far

  • It’s beginner-friendly and low maintenance: One of the things that impressed me most with Frontity is how relatively easy it is to jump into, even as a beginner. It installs with a couple of commands and takes care of all the setup and configuration for connecting to WordPress via the REST API—something I would have struggled with if left to my own devices.
  • It works with experimental block themes. In my very limited testing, Frontity’s framework works as expected with experimental block themes, just as it does with classic WordPress themes, like Twenty Twenty. I tested with the Quadrat theme that supports the experimental stuff the Gutenberg team is working on.
  • Hosting is good, but maybe too expensive: As Chris wrote, Frontity is “a perfect match for Vercel.” However, the current Jamstack pricing model that includes Vercel is unattractive for many ordinary WordPress users.
  • Frontity’s documentation is good, but could be better: The Frontity team recently reorganized Frontity documentation into tutorials, guides and an API reference. However, in my opinion it’s still confusing for those just getting into the framework.

Because I enjoyed this project so much, I am currently doing a theme project from scratch. Even in WordPress, I learned best by getting my hands dirty building WordPress themes from scratch.

While I am still doing my Gatsby and Frontity side projects, I have not lost my sight from the ongoing WordPress block editor and block-based theme development. At the time of writing, there are already sixteen block-based themes in the WordPress theme directory. I have just started exploring and understanding experimental block themes, which might be another interesting learning project.

After this project, my thoughts about Gatsby, Frontity and the concept of headless sites are still evolving. That’s only because it’s tough to make a fair comparison of when a lot of the tooling is actively in development and changing all the time. There are even experimental themes, that are much lighter and different structural markups than the current PHP-based classic themes, which might be a subject for yet another time.


Please share your experience and thoughts if you have been using Frontity in your projects. As always, I enjoy reading any comments and feedback!