这篇文章是在 Automattic 收购 Frontity 及其整个团队 之前进行的。根据 Frontity 的创始人的说法,该框架将过渡到社区领导的项目,并将该项目置于“稳定、无错误的职位”,并提供文档和功能。与其他开源社区项目一样,Frontity 将保持免费,因为它一直是免费的,并有机会为该项目做出贡献,使其成为一个更好的解耦 WordPress 框架。更多详细信息可以在 此常见问题解答页面 中找到。
在我的 上一篇文章 中,我们使用 Frontity 创建了一个无头 WordPress 网站,并简要介绍了其文件结构。在这篇配套文章中,我们将深入研究 @frontity/mars-theme
包或火星主题,并逐步介绍如何对其进行自定义以创建我们自己的主题。火星主题不仅是一个很棒的入门主题,还是 Frontity 的默认主题——有点像 WordPress Twenty Twenty-One 或者类似的东西。这使得它成为我们获得 Frontity 及其功能实践经验的完美起点。
具体来说,我们将查看 Frontity 火星主题的基本部分,包括他们称之为“构建块”以及该包附带的不同组件。我们将涵盖这些组件的功能、工作原理以及最后,如何使用示例进行样式设置。
准备好了吗?让我们开始吧!
目录
- 简介:Frontity 的构建块
- 第 1 节:深入了解火星主题
- 第 2 节:使用列表组件
- 第 3 节:链接、菜单和特色图片
- 第 4 节:如何为 Frontity 项目设置样式
- 第 5 节:自定义 Frontity 火星主题
- 第 6 节:资源和鸣谢
- 结论:总结和个人感想
Frontity 的构建块
让我们回顾一下我们在 上一篇文章 中创建的 Frontity 项目的文件结构,因为它向我们展示了在何处找到 Frontity 的构建块,frontity.settings.js
,package.json
和 packages/mars-theme
文件夹。我们之前详细介绍了这些内容,但特别是 package.json
文件为我们提供了大量关于该项目的信息,例如名称、描述、作者、依赖项等。以下是该文件包含的内容
frontity
: 这是包含 Frontity 应用程序开发中使用的所有方法的主要包。CLI 也位于此处。@frontity/core
: 这是最重要的包,因为它处理所有捆绑、渲染、合并、转译、服务等。我们无需访问它即可开发 Frontity 应用程序。完整列表在 Frontity 文档 中捕获。@frontity/wp-source
: 此包 连接到我们网站的 WordPress REST API 并获取火星主题中所需的所有数据。@frontity/tiny-router
: 此包 处理window.history
并帮助我们进行路由。@frontity/htmal2react
: 此包 将 HTML 转换为 React,与 处理器 一起工作,匹配 HTML 部分并用 React 组件替换它们。
Frontity 核心或 @frontity/package
(也称为 Frontity 的构建块)在其 @frontity/components 包中包含有用的 React 组件库,它导出有用的内容,例如 Link、Auto Prefetch、Image、Props、Iframe、Switch 以及其他函数、对象等,可以直接导入 Frontity 项目组件。这些组件的更详细描述(包括语法信息使用案例)在 此包参考 API 中。
Frontity 文档提供了有关 启动 Frontity 项目时会发生什么 的更多信息。
在启动 frontity 时,
frontity.settings.js
中定义的所有包都由@frontity/file-settings
导入,并且来自每个包的设置和导出由@frontity/core
合并到一个存储中,您可以在其中使用@frontity/connect
(Frontity 状态管理器)访问开发过程中不同包的state
和actions
。
接下来,我们将熟悉这些构建块、实用程序和导出如何在 火星主题包 中使用,以使用无头 WordPress 端点创建功能齐全的 Frontity 项目。
第 1 节:深入了解火星主题
在讨论样式设置和自定义之前,让我们简要熟悉一下火星主题(@frontity/mars-theme
)文件 结构 以及它是如何组合在一起的。
#! 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
火星主题有三个重要的组件文件:/src/index.js
文件、src/list/index.js
和 src/components/index.js
。Frontity 的文档是了解火星主题的绝佳资源,特别是对 不同火星主题组件如何在 Frontity 网站中定义和连接在一起 有着非常详细的介绍。让我们开始熟悉主题的三个最重要的组件:Root
、Theme
和 List
。
/src/index.js
)
主题 Root 组件(src/index.js
文件,也称为主题的 Root,是火星主题最重要的组件之一。Root 充当 入口点,它针对网站标记中的 <div id="root">
,以注入运行 Frontity 项目所需的所有已安装包的根目录。Frontity 主题在 DOM 中导出一个根目录以及其他必需的包,如以下用例示例 中从 Frontity 文档中所示
<!-- /index.HTML (rendered by Frontity) -->
<html>
<head>...</head>
<body>
<div id="root">
<MyAwesomeTheme />
<ShareModal />
<YetAnotherPackage />
</div>
</body>
</html>
此 Frontity 文档 解释了 Frontity 如何使用称为 Slot 和 Fill 的可扩展性模式扩展其主题。Root
组件(/src/index.js
)的示例 取自其火星主题包(@frontity/mars-theme
)。
这是该包在初始化 Root
组件时引入的所有内容
// 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;
火星主题 Root 组件 导出 包,这些包 包括 任何 roots、fills、state、actions 和 libraries 元素。有关 Root
的更多详细信息,请参阅 此 Frontity 文档。
/src/components/index.js
)
主题组件(Frontity Theme
组件 是其主要根级组件,由 Theme
命名空间导出(第 12-16 行,在之前的示例中突出显示。Theme
组件用 @frontity/connect
函数(第 51 行,在下面突出显示)包装,该函数提供对来自 Root
组件实例的 state
、actions
和 libraries
属性的访问,并允许 Theme
组件读取 state
,通过 actions
进行操作,或使用库中其他功能包的代码。
// 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`
// ...
`;
此示例直接从Mars 主题的/src/components/index.js
组件中提取,该组件是使用 frontity 的connect
导入的(上面第 4 行)。我们正在使用state.source.get()
从当前路径(上面突出显示的第 39-46 行)检索要呈现的data
;例如,List
、Post
和其他组件。
第二部分:使用 List 组件
我们刚才看到的只是 Frontity 的 Mars 主题中主题级别的组件。您可能已经注意到,这些组件会导入额外的组件。让我们看一下其中一个具体的组件,即List
组件。
List
组件由src/components/list/index.js
导出,该组件使用@loadable/components
以将 List
组件代码拆分为多个代码块,以便组件仅在用户单击 List 视图时加载;否则,它将根本不会呈现,就像单击 Post 视图时一样。
// 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"));
在此示例中,Frontity 使用loadble
函数(从Loadable components 集成)进行代码拆分,该函数异步加载组件并将代码分离到不同的捆绑包中,这些捆绑包在运行时动态加载。Frontity 的核心包 API 参考 对此进行了更详细的介绍。
显示帖子列表
要在存档页面中显示帖子列表,首先要查看 Frontity 的src/components/list/list.js
组件。顾名思义,List
组件使用state.source.get(link)
及其items
字段(下面突出显示的第 22-25 行)呈现帖子列表。
// 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);
在上面的代码示例中,connect
函数由 frontity 在第 2 行导入,并围绕第 31 行(最后一行)中导出的connect(List)
组件进行包装。另外还导入了两个组件,list-item.js
和pagination.js
。接下来我们将看一下它们!
以下是list-item.js
的内容
// 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);
Item
组件 呈现博客帖子的预览,其中包含可点击的帖子标题(上面突出显示的第 12-14 行)、作者姓名(上面突出显示的第 19-21 行)和发布日期(上面突出显示的第 25-28 行),以及<FeaturedMedia />
,它作为帖子的可选特色图像。
对帖子列表进行分页
让我们看一下Pagination
组件,该组件之前在 List 组件中通过src/components/list/pagination/js
呈现,该组件的内容如下
// 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);
Pagination
组件用于让用户可以在帖子列表之间进行分页 - 您知道,就像从第 1 页导航到第 2 页,或者从第 2 页导航到第 1 页一样。state
、actions
、libraries
属性由全局上下文提供,该上下文使用connect(Pagination)
将它们包装并导出。
显示单个帖子
Post
组件 显示单个帖子和页面。实际上,这两个组件在结构上是一样的,只是在帖子中,我们通常会显示元数据(作者、日期、类别等)。页面中通常不会使用元数据。
在此 Post
组件中,条件语句仅在post
对象包含数据(即data.isPost
)且在主题的根组件中sate.theme.featured
中选择了特色图像时才会呈现。
// 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);
第三部分:链接、菜单和特色图像
我们刚刚看到了List
组件在显示一组帖子时是多么重要。它可以与我们在使用WordPress 循环 处理存档页面、最新帖子提要和其他帖子列表时通常使用的标记进行对比。
在进入 Mars 主题样式之前,还有几个组件值得我们关注。
src/components/link.js
)
Link 组件(以下MarsLink
组件来自src/components/link.js
,它是{@link Link}
组件的包装器。它接受 与{@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 });
如本教程 中所述,Link
组件提供一个link
属性,该属性以目标 URL 作为其值。引用文档中的内容:它在生成的 HTML 中输出一个
<a>
元素,但不会强制页面重新加载,而这正是您简单添加<a>
元素而不是使用Link
组件所会发生的。"
src/components/nav.js
)
Frontity 菜单(前面,我们在frontity.settings.js
文件中定义了菜单项的值。在Nav
组件(位于src/components/nav/js
中)中,会对这些菜单项值进行迭代,匹配它们的页面url
,并在Header
组件中显示该组件。
// 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);
Mars 主题提供了另外两个菜单组件 - menu.js
和menu-modal.js
- 用于移动设备视图,这些组件与nav.js
一样,可从Mars 主题 GitHub 存储库 中获取。
/src/components/featured-media.js
)
特色图像组件(在 Frontity 中,特色媒体项的值是在前面讨论过的Root
组件的theme.state.featured
行中定义的。其完整代码位于/src/components/featured-media.js
组件文件中。
现在,我们已经熟悉了 Mars 主题及其构建块、组件和函数,可以开始学习为 Mars 主题前端提供的不同样式方法了。
随着我们的深入学习,您可能会发现此Frontity 文档 是对我们涵盖的各种样式方法的良好参考。
第四部分:如何为 Frontity 项目设置样式
对于我们这些来自 WordPress 的用户来说,Frontity 中的样式设置看起来和感觉起来与在典型的 WordPress 主题中覆盖样式 的各种方法有所不同。
首先,Frontity 为我们提供了使用 styled-components 制作的可重用组件,以及Emotion,一个用于在 JavaScript 中为组件设置样式的 CSS 库,开箱即用。Emotion 在 React 和 JavaScript 开发人员中很受欢迎,但在 WordPress 社区中并不常见,至少根据我所见是这样的。CSS-Tricks 对CSS-in-JS 进行了详细的介绍,包括它与其他样式设置方法的对比,以及本视频 提供了有关该库的背景信息。因此,了解 styled-components 和 Emotion 都可以使用,这是一个很好的起点。
Frontity 的文档为为 frontity 组件设置样式 提供了很棒的学习资源,以及自定义 Frontity 主题样式的分步指南。
我刚接触 CSS-in-JS 世界,除了偶尔阅读一些相关资料。我在一个 Gatsby 项目中接触到了 CSS-in-JS 样式,但是 Gatsby 提供了许多其他样式选项,这些选项在 Frontity 或 Mars 主题中并不常见。也就是说,我觉得自己能够克服缺乏经验的困难,并且从探索过程中学到了如何构建项目。
因此,我们将探讨一些样式示例,并参考 Frontity 的样式文档,以深入了解更多信息。
使用 styled-components
顾名思义,我们需要一个组件来对其进行样式设置。因此,首先,让我们使用 Emotion 的 styled
函数创建一个 styled-component。
假设我们想要为一个可复用的 <Button />
组件设置样式,该组件在我们的 Frontity 项目中被广泛使用。首先,我们应该创建一个 <Button />
组件(在其 div
标签后面添加一个点),然后使用模板字面量调用该组件来设置字符串样式。
// Creating Button styled component
import { styled } from "frontity"
const Button = styled.div`
background: lightblue;
width: 100%;
text-align: center;
color: white;
`
现在,这个 <Button />
组件可以在其他组件中导入。让我们具体看一下 Mars 主题 的 <Header />
组件,看看 styled-component 在实际应用中的使用方式。
// 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;
`;
在上面的代码示例中,<StyledLink />
组件(第 39-41 行,如上突出显示)用于为另一个组件 <Link />
设置样式。类似地,<Container />
和 <Title />
styled-components 用于为站点标题和站点主容器宽度设置样式。
The Emotion 文档 描述了如何使用 styled-component,只要它接受 className
属性即可。这是一个有用的样式工具,可以使用变量来扩展,如下面的示例所示 来自 Frontity 文档
// 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};
`;
上面的 styled
组件在 Mars 主题中被广泛使用。但在我们深入研究之前,让我们看看如何使用 CSS 属性来设置组件的样式。
使用 CSS 属性
The css
属性 作为 Frontity 核心包 中的模板字面量,可用于内联样式。它 类似 于 styled-components,不同之处在于 css
不返回一个 React 组件,而是一个特殊的对象,可以通过 css
属性传递给组件。
/* Using as CSS prop */
import { css } from "frontity";
const PinkButton = () => (
<div css={css`background: pink`}>
My Pink Button
</div>
);
看到了吗?我们可以使用组件上的 css
属性对组件进行内联样式设置。更多用例示例可在 Emotion 文档 中找到。
<Global />
组件
使用 <Global />
是一个 React 组件,允许我们创建站点范围的通用样式,尽管 Frontity 并没有针对性能对其进行优化。全局样式应该添加到 <Theme />
根组件中。
// 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`
// ...
`;
The <Global />
component 有一个 style
属性,它接受一个 css
函数作为其值,并且包含在反引号(第 35-45 行,如上突出显示)中的标准 CSS 作为模板字面量。Frontity 建议 使用全局样式来设置全局使用的 HTML 标签的样式,例如 <html>
、<body>
、<a>
和 <img>
。
其他 CSS 样式选项 - 包括 动态 CSS 属性 和 React 样式属性 - 在这个 Frontity 样式指南 中有描述。
自定义 Frontity 主题的资源
我在开始我的 Mars 主题项目时做了很多研究,并想分享一些我认为对设置 Frontity 主题样式最有用的资源。
- 官方 Frontity 主题。除了默认的 Mars 主题,Frontity 还有一个现成的包,它将默认的 WordPress Twenty Twenty 主题完整地移植到一个 Frontity 项目中。你会在下一节中注意到我的样式自定义灵感来自于这个很棒的学习资源。
- 社区主题。在撰写本文时,共有 九位 Frontity 社区成员贡献了功能齐全的主题包。这些主题可以克隆到你的项目中,并根据你的需要进行自定义。同样,在 Frontity 展示页面 中包含的许多网站都有 GitHub 仓库链接,就像我们可以从 WordPress 主题中复制或获取设计提示一样,我们可以使用这些资源,通过参考这些包来自定义我们自己的 Frontity 主题。
- 从头开始创建你自己的主题。 Frontity 教程网站 有一个非常棒的逐步指南,可以教你从头开始创建一个功能齐全的主题包。虽然整个过程可能有点耗时,但它是完全理解 Frontity 网站项目的最佳方式。
现在我们已经介绍了 Frontity 中一些常用的样式设置技术,让我们将学到的知识应用到实践中,开始自定义我们的 Mars 主题项目。
第 5 节:自定义 Frontity Mars 主题
我将分享我的一个 Frontity 项目,我以 Mars 主题为基础,并使用我们之前介绍的资源对其进行了修改。因为这是一个学习项目,我花时间学习了 Frontity 默认主题、社区主题 和 Frontity 展示网站。
以下是关于如何为我的无头 WordPress 网站项目自定义 Frontity 的 Mars 主题的示例。
更改主题包名称
首先,我想更改 @frontity/mars-theme
包名称。最好更改包名称,并确保包文件中的所有依赖项都更新。Luis Herrera 在 Frontity 社区论坛中概述了重命名 Mars 主题包所需的步骤,我将其作为参考,将 @fontity/mars-theme
包更改为 @frontity/labre-theme
。
因此,打开 package.json
文件,并将第 2 行的 name
属性更改。这是整个项目中使用的包名称。

package.json
文件中将我的项目从 mars-theme
重命名为 labre-theme
。我们还应该更新项目文件夹的名称。可以在第 25 行完成此操作。我将我的文件夹从 ./package/mars-theme
更改为 ./package/labre-theme
。现在,主题包被正确列为依赖项,并将被导入到项目中。
我们的 frontity-settings.js
文件需要反映名称更改。因此,让我们打开它,并
- 将第 13 行的包名称重命名(我将我的从
@frontity/mars-theme
更改为@frontity/labre-theme
),并 - 将第 3 行的名称重命名(我将我的从
mars-demo
更改为labre-demo
)。
// @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/"]
],
// ...
接下来,我们要使用这些更改重新初始化项目。我们应该使用终端中的 rm -rf node_modules
删除 node_modules
文件夹,并使用 yarn install
重新安装 npm 包。 npm 包重新安装后,所有内容都将在内部正确链接,并且我们的 Frontity 项目可以正常运行,没有任何错误。
重构导航,使用动态菜单获取
正如我们之前讨论的,Frontity 菜单项要么硬编码在 frontity.setting.js
文件中,要么在存储在 Frontity state
中的 index.js
组件中。但是,WordPress 可以动态获取 Frontity 菜单。事实上,Frontity 碰巧有一个关于 这个主题的 YouTube 视频。让我在这里分解关键步骤。
第一步是在 WordPress 中安装 WP-REST-API V2 Menus 插件。该插件在 WordPress 插件目录中免费提供,这意味着您可以直接从 WordPress 管理员找到并激活它。
为什么我们需要这个插件?它将所有已注册的 WordPress 菜单的新路由扩展到 REST API(例如 /menus/v1/menus/<slug>
)。

/wp-json/menu/v1/menus
处检查我们的项目网站,它应该在 JSON 中显示我们选择的菜单项。我们可以使用菜单项的 slug
属性获取菜单项。接下来,让我们使用 教程 中的 menuHandler
函数。在 src/components/handler/menu-handler.js
处创建一个新的 menu-handler.js
文件,并将以下代码粘贴进去
// 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;
此 menuHandler
函数仅在 pattern
值(即 /menu/:slug
)匹配时执行。现在让我们更新我们的 /src/index.js
根组件,以便它导入处理程序
// 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;
在 source
属性下添加一个处理程序数组,并在 beforeSSR
函数之前获取数据。它不获取数据,但确实匹配 menu-handler
slug,这意味着 menuHandler()
被执行。这会将菜单项放入状态,并且它们可以被操作。
请注意,我们在这里添加了一个新的 menuUrl
属性(上面的第 15 行),它可以在处理程序中的端点以及 nav.js
组件中用作变量。然后,通过更改 index.js
根组件中 menuUrl
的值,我们可以显示另一个菜单。
让我们通过状态将这些数据获取到我们的主题中,并与 menu-items
映射以在网站上显示。
// 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;
// ...
如果我们在这里和 index.js
中更改我们的 menu
slug,那么我们将获得不同的菜单。要在移动视图中查看动态菜单项,我们应该类似地 更新 menu-modal.js
组件。
此外,本教程还描述了如何获取嵌套菜单,您可以从教程视频中了解,从大约 18:09 开始。
修改文件结构
我决定重新组织我的 Labre(以前称为 Mars)主题文件夹。以下是更改后的外观
#! 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/
// ...
如您所见,我为页面、样式、标题、帖子和图像添加了单独的文件夹。请注意,我们必须在 index.js
和其他相关组件中更新文件路径,以随时更改文件和文件夹的组织方式。否则,它们将指向空!
添加自定义页脚组件
您可能已经注意到,原始的 Mars 主题文件夹结构既不包含页脚组件,也不包含单独的页面组件。让我们创建这些组件来演示我们的新文件夹结构是如何工作的。
我们可以从页面组件开始。Mars 主题默认情况下使用 posts.js
组件生成页面和帖子 - 这是因为页面和帖子本质上是一样的,只是帖子具有元数据(例如作者、日期等),并且它们可以摆脱它。但是,我们可以通过复制 posts.js
中的代码并将其粘贴到我们 /pages
文件夹中的一个新的 pages.js
文件中来为我们自己的需求将它们分开。
// 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;
`
// ..
我们在这里所做的只是从 post.js
(第 31-34 行和 55-76 行)中删除元数据以及相应的 styled
组件。正如我们在 Mars 主题 /list
文件夹 中所做的那样,我们应该在页面和帖子文件夹中导出 loadable
函数,以便对 <List />
组件进行代码分割。这样,如果用户处于单个帖子中,则不会显示 <List />
组件。
// 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"));
接下来,我们应该更新 /src/components/index.js
组件的路径 url,如下所示
// 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
现在我们正在导入 <Page /
组件,并且已添加了我们的 <Main />
样式组件。
让我们继续我们的自定义页脚组件。您可能已经知道该怎么做:创建一个新的 footer.js
组件文件,并将其放入 /src/components/footer/
文件夹中。我们可以向我们的页脚添加一些小部件,这些小部件显示网站地图和某种“由...提供支持”的摘要
// 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);
// ...
这是一个超级简单的示例。请注意,我已经导入了一个 <Widget />
组件(第 4 行,上面突出显示)并调用了该组件(第 9 行,上面突出显示)。我们实际上还没有 <Widget />
组件,所以让我们在做的时候创建它。那可以在与页脚相同的目录 /src/components/footer/
中的 widget.js
文件中。

widget.js
组件的灵感来自 Aamodt Group 的 页脚组件,它在 GitHub 存储库 中可用。
自定义主题标题
Mars 主题中的默认 header.js
组件非常基本,带有一个站点标题和站点描述以及下面的导航项。我想用左侧的网站徽标和标题以及右侧的 nav.js
组件(顶部导航)重构标题组件。
// 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;
`;
// ...
我重构的 header.js
组件导入了一个徽标图像(上面的第 6 行,突出显示)并在第 14 行使用它。下面显示的 nav.js
组件基本上是一样的,只是有一些细微的样式修改。

<Global>
样式组件
添加 我们已经介绍了 <Global>
组件以及它如何用于网站范围的 CSS。默认的 Mars 主题根组件中只有几个全局样式,我想添加更多。
我通过在 /src/components/styles/globalStyles.js
处使用一个单独的 globalStyles
文件来做到这一点 - 类似于 Frontity 的 Twenty Twenty 主题 - 并添加了根变量、CSS 重置以及常见的网站范围的元素样式,在 GitHub 存储库中找到。
实现流畅的排版
即使它并不真正属于范围,我真的很想在我的自定义主题中使用流畅的排版,作为我整体学习之旅的一部分。因此,我将其添加到全局样式中。
CSS-Tricks 已经广泛涵盖了 流畅的排版 以及 如何 clamp()
函数用于设置目标字体大小。参考了这些 CSS-Tricks 文章以及 这篇 Picalilli 文章 作为我的指南,我在 globalStyles.js
组件中的 :root
元素上定义了两个自定义属性,其中包含夹住的字体大小范围。
// src/components/styles/globalStyles.js
:root {
--wide-container: clamp(16rem, 90vw, 70rem);
--normal-container: clamp(16rem, 90vw, 58rem);
}
wide-container
包装器用于标题和页脚组件,而 normal-container
将用于显示帖子和页面。
我还夹住了 globalStyles.js
组件中的 elementBase
下的标题,如这个 GitHub 存储库中所示。
使用 clamp()
函数很有趣,因为它意味着我可以设置一个尺寸范围,而无需任何媒体查询!
向主题添加网络字体
我还想在我的主题中使用不同的网络字体。使用 @font-face
在 CSS 中导入网络字体,在 CSS-Tricks 上有介绍。Frontity 的 Twenty Twenty 主题使用了它,因此这也是一个很好的参考点。
我想要三种 Google 字体
- Source Sans Pro 用于标题
- PT Serif 用于正文
- PT Sans Narrow 用于元数据
我们可以使用这些字体,要么使用 HTML 头部的 <link>
标签,要么使用 CSS 中的 @import
语句。但 Chris 介绍了如何使用 Google Fonts 的 @font-face
语句,这使得我们可以优化 HTTP 请求次数,因为我们可以将字体下载到我们自己的服务器上。
我使用 Google webfonts helper 来托管下载的字体文件。以下是我得到的字体文件
/* 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 */
}
参考 Twenty Twenty 主题的 实现方式,我创建了一个 font-face.js
文件,并将其放在 /src/components/styles
文件夹下,如这个 GitHub 仓库所示。
这些字体指向一个不存在的 /fonts
文件夹。因此,让我们创建一个这样的文件夹,并确保所有正确的字体文件都在里面,以便字体能够正确加载。
globalStyles
和 @face-font
组件导入到根 <Theme />
组件中
将 让我们打开我们的主题根组件 /src/components.index.js
,并在其中添加我们的 globalStyles.js
和 font-face.js
组件。如下所示,我们应该将这两个组件导入到 index.js
中,并在稍后调用它们。
// 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 */}
// ...
最后,我们应该从 index.js
中删除 mars-theme 的 globalStyles
组件。现在,我们的新字体将应用到整个项目中。
样式化页面和文章
我们的文章和页面已经基本上完成了样式化,除了某些 Gutenberg 块的内容,比如按钮、引号等。
为了对文章的元数据进行样式化,让我们为作者、日期、类别和标签添加图标。 Frontity 对 WordPress Twenty Nineteen 主题的移植 使用 SVG 图标和组件来实现 author.js
、categories.js
、posted-on.js
和 tags.js
组件,我们可以完全复制这些组件并在我们自己的项目中使用它们。我实际上是复制了顶层的 entry-meta
文件夹及其中的所有内容,从 frontity-twentynineteen 主题中,并将其添加到 /components/posts/
项目文件夹中。
接下来,我们应该更新我们的 src/components/list/list-item.js
组件,以便我们可以使用新的资源。
// 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);
<EntryMeta />
组件的样式化组件可以像 GitHub 仓库所示。
有了这些样式,我们的归档页面中的文章元数据看起来不错,在文章元数据分类(作者、发布时间)之前显示了图标。
在这里,我们将使用更具描述性的标题修改归档分类页面的样式。让我们像下面所示那样更新 /src/components/list/list.js
中的 list.js
组件。
// 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);
`;
// ...
在上面的示例中,我们用 PageDesctiption
样式化组件包装了 taxonomy.id data
,并应用了一些样式规则。
默认 Mars 主题中的文章分页非常基础,几乎没有样式。让我们再次借鉴 Frontity Twenty Nineteen 主题,通过复制 pagination.js
组件文件,并将它粘贴到我们主题中的 /src/components/list/pagination.js
中,来添加分页组件和样式。

为了自定义实际的单个文章和页面,让我们创建一个居中的加粗标题,并显示文章元数据。
// 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);
// ...

添加 Gutenberg 块样式
WordPress 使用一个单独的样式表来为块编辑器中的块进行样式化。现在,这个样式表还没有被使用,但如果我们能够在其中添加一些基础样式,用于我们添加到页面和文章中的各种块内容,那就太好了。

.wp-block-buttons
类是在我们目前没有使用… 的 WordPress 块样式表中声明的。WordPress 块编辑器使用两个样式文件:style.css
和 theme.css
。让我们直接从 Frontity 对 Twenty Twenty 主题的移植中复制这些文件,因为它们就是那样实现了 WordPress 的样式。我们可以将它们放在 /styles/gutenberg/
文件夹中。
“Gutenberg” 是 WordPress 块编辑器在开发时使用的代号。它有时仍然被称为“Gutenberg”。
让我们将这两个样式文件添加到我们的主题根组件 /src/components/index.js
中,就像我们之前对 globalStyles
所做的那样。
// src/components/index.js
import gutenbergStyle from "./styles/gutenberg/style.css";
import gutenbergTheme from "./styles/gutenberg/theme.css"
以下是我们更新后的 <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 */}
// ...
我们可以通过多种不同的方式来覆盖样式。我选择了一种简单的方法。例如,要覆盖页面和文章的样式化组件中的按钮样式(.wp-block-buttons
)。

我们可以用相同的方式覆盖任何其他块样式。在 Frontity 的 Twenty Nineteen 主题中,来自 WordPress 版本主题的整个样式表被 添加到 Frontity 版本 中,以复制完全相同的界面。Frontity 的 Twenty Twenty 移植只使用了 WordPress Twenty Twenty 主题中的一些样式, 但作为内联样式。
其他样式资源
在本节中我们介绍的所有关于样式化的资源都 在 GitHub 仓库中提供。如果您想进一步扩展我的 @frontity/labre-theme
项目,以下是我收集的一些资源。
- 暗黑模式:Frontity 展示库中有两个示例,goiblas/personal-blog 和 aamodtgroup,是暗黑模式功能的很好的参考。还有一个关于 如何在 Frontity 项目中实现暗黑模式 的教程。
- Contact Form 7:这个方便的小型 WordPress 插件可以被集成。 来自 Frontity 社区的这个教程 描述了如何做到这一点。
- 评论:原生 WordPress 的评论功能在 这个指南 中有描述。
- 无限滚动钩子:这个 Frontity 演示项目 展示了如何在
@frontity/hooks
包中使用无限滚动钩子。这里有一个 YouTube 视频 来介绍它。 - Yoast SEO:这是一个超级流行的 WordPress 插件,我相信你们中的许多人会想在 Frontity 中使用它。请参考 这个
@frontity/package
文档,它会自动获取并渲染插件在 REST API 中公开的所有标签。
第 6 节:资源和鸣谢
有很多资源可以帮助你学习和自定义你的 Frontity 项目。在准备这篇文章时,我广泛参考了以下资源。请参阅原始文章以获得更详细的信息。
Frontity 文档和文章
- 分步教程(Frontity):如果您是 Frontity 的新手,或者您以前使用过 Frontity,但想提升自己的水平,这里是一个完美的起点。
- 概念指南(Frontity):这些指南有助于解决在使用与 WordPress 连接的 React 应用程序进行动态服务器端渲染时遇到的常见挑战。
- Frontity API 参考(Frontity)。其中包含关于 Frontity CLI、包、插件和主题的详细信息。一旦你掌握了使用 Frontity 的基本知识,这里就是你使用 Frontity 进行项目开发时可能花费大部分时间的页面。”
- Frontity 示例库(Frontity):这是一个 Frontity 项目的集合,展示了 Frontity 在实际应用中的使用方式。
其他文章和教程
- 使用 Frontity 和 WordPress 构建博客 (Jesús Olazagoitia)
- 如何在 30 分钟内创建 React WordPress 主题 (Reyes Martinez)
- Frontity 入门 (Dylan Tientcheu)
- 连接 Gutenberg 和 Frontity (Mario Santos,Frontity 产品经理) 这篇文章基于 Mario 在 2020 年 WordPress JavaScript 大会上的演讲,并附带视频。
Frontity 案例研究
- 迁移到 Frontity:Diariomotor 案例研究 (Reyes Martinez):了解 Frontity 如何帮助 Diariomotor 发展,减少开发时间并使其走上更好的性能之路。
- 将 Aleteia 迁移到 Frontity (Reyes Martinez)。Aleteia 是天主教新闻的领先网站。Frontity 允许他们在短短几个月内迁移到现代前端堆栈。
- 为 Frontity 引入 AWSM F1 主题 (Venuraj Varma)。Awsm Innovations 使用 Frontity 重建了他们的网站,以提升网页性能并提供出色的用户体验。
- 案例研究:使用 Frontity 将 Gudog 的博客增长 88% (Reyes Martinez):Frontity 帮助 Gudog 将其自然流量增加了 88%,并显着改善了其 SEO 结果。
Frontity 演讲和视频
- 如何在 Frontity 中获取 WordPress 菜单 (Michael Burridge)。在本视频中,Michael 解释了如何使用 WordPress WP-REST-API V2 菜单插件动态获取 WordPress 菜单项。
- 如何使用 Frontity 使用 React 创建无头 WordPress 主题 (YouTube)
- 教程 - hello-frontity 教程研讨会。这是一个包含在 tutorial.frontity.org 上提供的分步教程中的学习项目。
- 连接 Gutenberg 和 Frontity:案例研究 (Mario Santos)。在本演讲视频中,Frontity 产品经理 Mario 解释了如何使用 WordPress 块编辑器和 Frontity 重建官方 Frontity 网站,同时强调了沿途遇到的所有挑战和经验教训。
- Frontity YouTube 频道
Frontity 社区
Frontity 拥有一个充满活力和参与度高的 社区论坛,供您提出有关 Frontity 项目的问题或寻求帮助。
总结和个人想法
如果您还没有从这篇文章或我写的其他文章中了解到,我对无头 WordPress 网站充满热情。正如我在 之前的一篇文章 中写的那样,我通过 Chris 发布这篇文章 时偶然发现了 Frontity。我已经尝试了它超过六个月,选择深入研究 Frontity 及其默认 Mars 主题中使用的构建块。我必须承认,它是一个引人入胜的软件框架,并且我获得了愉快的学习体验。我甚至可能会在我的个人网站上使用这种设置!
以下是我迄今为止使用 Frontity 的一些主要收获
- 它对初学者友好且维护成本低:让我对 Frontity 感到印象最深刻的一件事是,即使是初学者也很容易上手。它可以通过几个命令安装,并负责通过 REST API 连接到 WordPress 的所有设置和配置——如果让我自己处理,我会很头疼。
- 它与实验性块主题兼容。在我的有限测试中,Frontity 的框架与实验性块主题以及 Twenty Twenty 等经典 WordPress 主题一样按预期工作。我用 Quadrat 主题 进行了测试,它支持 Gutenberg 团队正在开发的实验性内容。
- 托管很好,但可能太贵了:正如 Chris 写的那样,Frontity 是“与 Vercel 的完美匹配”。但是,目前 Jamstack 定价 模型(包括 Vercel)对于许多普通 WordPress 用户来说 不可取。
- Frontity 的文档很好,但可以更好:Frontity 团队最近将 Frontity 文档重新组织为 教程、指南 和 API 参考。但是,在我看来,对于那些刚接触该框架的人来说,它仍然很混乱。
由于我非常喜欢这个项目,我目前正在从头开始进行一个主题项目。即使在 WordPress 中,我通过从头开始构建 WordPress 主题来学习效果最好。
虽然我还在做我的 Gatsby 和 Frontity 侧边项目,但我并没有忽视正在进行的 WordPress 块编辑器 和 基于块的主题 开发。在撰写本文时,WordPress 主题目录中已经有 16 个基于块的主题。我刚刚开始探索和理解 实验性块主题,这可能另一个有趣的学习项目。
在这个项目之后,我对 Gatsby、Frontity 和无头网站概念的看法仍在不断变化。这仅仅是因为很难进行公平的比较,因为很多工具都在积极开发中,并且一直在变化。甚至有 实验性主题,它们比当前基于 PHP 的经典主题更轻量级,并且 结构标记不同,这可能是另一个话题了。
如果您已经在您的项目中使用 Frontity,请分享您的经验和想法。一如既往,我喜欢阅读任何评论和反馈!
感谢您的撰写!我将保存它以备将来参考。
我正要开始使用 Frontity 的项目,但现在我有点犹豫……您认为现在使用它是一个好的选择,还是我应该寻找其他东西?我读了他们关于公告的官方博客文章,基本上是“我们放弃了这个项目,各位自求多福……”
:/
除了他们的 公告 文章,我没有其他信息。
如果你问我的个人意见,我并不介意在我的个人网站上使用它,因为一旦我发现该框架不稳定,我总是可以恢复到 WordPress 主题或其他框架。
我不为客户工作,但如果我是,我会等待并观察使用 Frontity 的 商业网站,让尘埃落定,然后再决定是否在商业网站上使用 Frontity。
非常感谢您的回答和见解,Ganesh。这次是一个商业项目。
另一个竞争者是 Atlas,所以我还是可能会用 Frontity 来做。许多开源项目都蓬勃发展,所以并非全盘皆输。
好的一点是,即使 Frontity 被遗忘了,我们网站的无头后端也能完美运行,我们可以将其与不同的前端解决方案结合起来。