Island.js 内置一套开箱即用的默认主题,且内部的组件也提供了配置来自定义,包括:
你可以点进对应的页面查看具体的配置项。本文所要介绍的是如何开发一个自定义主题。
大部分情况下,你并不想从零开始开发一个主题,而是想基于默认主题进行扩展,这时候可以参考下面的方式进行主题开发。
TIP
如果你想从头开发一个自定义主题,可以前往【重新开发自定义主题】。
默认情况下,你需要在 .island
目录下创建一个 theme
目录,然后在这个目录下创建一个 index.ts(x)
文件,这个文件就是你的主题入口文件:
.island
├── config.ts
└── theme
└── index.tsx
你可以使用如下的方式来书写 theme/index.tsx
文件:
// `theme/index.tsx`
import React from 'react';
import {
Layout as DefaultLayout,
NotFoundLayout,
HomeLayout,
setup
} from 'islandjs/theme';
// 添加一些自定义的样式
import './custom.css';
const Layout = () => <DefaultLayout beforeHero={<div>beforeHero</div>} />;
// 导出三个组件和 setup 函数
export { Layout, HomeLayout, NotFoundLayout, setup };
可以看到,你能从islandjs/theme
中获取并导出默认主题的各个组件,包括:
值得注意的是,Layout 组件设计了一系列的 props 支持插槽元素,你可以通过这些 props 来扩展默认主题的布局,比如将上面的 Layout 组件改成如下的形式:
import { Layout as DefaultLayout } from 'islandjs/theme';
// 以下展示所有的 Props
const Layout = () => (
<DefaultLayout
/* Home 页 Hero 部分之前 */
beforeHero={<div>beforeHero</div>}
/* Home 页 Hero 部分之后 */
afterHero={<div>afterHero</div>}
/* Home 页 Features 部分之前 */
beforeFeatures={<div>beforeFeatures</div>}
/* Home 页 Features 部分之后 */
afterFeatures={<div>afterFeatures</div>}
/* 正文页 Footer 部分之前 */
beforeDocFooter={<div>beforeDocFooter</div>}
/* 正文页最前面 */
beforeDoc={<div>beforeDoc</div>}
/* 正文页最后面 */
afterDoc={<div>afterDoc</div>}
/* 左上角导航栏标题之前 */
beforeNavTitle={<span>😄</span>}
/* 左上角导航栏标题之后 */
afterNavTitle={<div>afterNavTitle</div>}
/* 右侧大纲栏上面 */
beforeOutline={<div>beforeOutline</div>}
/* 右侧大纲栏下面 */
afterOutline={<div>afterOutline</div>}
/* 整个页面最顶部 */
top={<div>top</div>}
/* 整个页面最底部 */
bottom={<div>bottom</div>}
/>
);
要扩展默认主题的组件,除了插槽,你还可以自定义 Home 页面及 404 页面组件,比如:
import { Layout, setup } from 'islandjs/theme';
// 定制 Home 页面
const HomeLayout = () => <div>Home</div>;
// 定制 404 页面
const NotFoundLayout = () => <div>404</div>;
export { Layout, HomeLayout, NotFoundLayout, setup };
当然,在开发过程可能需要使用页面的数据,你可以通过 usePageData
这个 Hook 来获取。
当然,如果你要从头开始开发一个自定义主题,你需要了解一下主题的组成。
默认情况下,你需要在 .island
目录下创建一个 theme
目录,然后在这个目录下创建一个 index.ts(x)
文件,这个文件就是你的主题入口文件:
.island
├── config.ts
└── theme
└── index.tsx
在theme/index.tsx
文件中,你需要导出一个 Layout 组件,这个组件就是你的主题的入口组件:
// theme/index.tsx
function Layout() {
return <div>Custom Theme Layout</div>;
}
// setup 函数,会在页面初始化时调用,一般用来做全局事件的监听,可为空函数
const setup = () => {};
// 导出 Layout 组件和 setup 函数
// 两者必须导出
export { Layout, setup };
这个 Layout 组件会被 Island.js 用来渲染整个文档站点的布局,你可以在这个组件中引入你的自定义组件,比如:
// theme/index.tsx
import { Navbar } from './Navbar';
function Layout() {
return (
<div>
<Navbar />
<div>Custom Theme Layout</div>
</div>
);
}
export { Layout };
// theme/Navbar.tsx
export function Navbar() {
return <div>Custom Navbar</div>;
}
那么问题来了,主题组件是如何获取页面数据和正文 MDX 组件内容的呢?这就需要用到 Island.js 的 Runtime API
了。
在主题开发的过程中,我们一般需要如下的一些关键 API:
用户获取页面所有数据的 Hook,比如:
import { usePageData } from 'islandjs/runtime';
function Layout() {
const pageData = usePageData();
return <div>{pageData.title}</div>;
}
usePageData
类型如下:
const usePageData: () => PageData;
PageData
的类型如下:
export interface PageData {
// 站点通用信息,包含主题配置 themeConfig 信息
siteData: SiteData;
// 上次更新时间
lastUpdatedTime?: string;
// 页面标题
title?: string;
// 页面描述
description?: string;
// Front Matter 元数据
frontmatter?: FrontMatterMeta;
// 页面类型
pageType: PageType;
// TOC 数据,包含标题和对应的锚点
toc?: Header[];
// 当前路由路径
routePath: string;
// 页面的路径,去掉 query 和 hash
pagePath: string;
// 页面的正文内容
content?: string;
}
基于此 API,你基本可以获取到需要的所有数据。
获取正文 MDX 组件内容,即正文内容:
import { Content } from 'islandjs/runtime';
function Layout() {
return (
<div>
<Content />
</div>
);
}
Island.js 内部用 react-router-dom 来实现路由,所以你可以直接使用 react-router-dom
的 Hook,比如:
import { useLocation } from 'islandjs/runtime';
function Layout() {
const location = useLocation();
return <div>Current location: {location.pathname}</div>;
}