Skip to content

Commit

Permalink
feat: html head 自定义
Browse files Browse the repository at this point in the history
  • Loading branch information
c0dedance committed Oct 30, 2023
1 parent 4b0220f commit e98f60a
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 23 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"ora": "^7.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "1.3.0",
"react-router-dom": "6.4.3",
"rehype-autolink-headings": "^7.0.0",
"rehype-slug": "^6.0.0",
Expand Down
46 changes: 43 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 15 additions & 6 deletions src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { createVitePlugins } from './vitePlugins'
import type { InlineConfig } from 'vite'
import type { RollupOutput } from 'rollup'
import type { HelmetData, HelmetServerState } from 'react-helmet-async'
import type { SiteConfig } from 'shared/types'
import type { Route } from './plugin-routes'
import type { RenderResult } from 'runtime/ssr-entry'
Expand All @@ -39,7 +40,7 @@ export async function renderPage({
routes,
}: {
root: string
render: (pagePath: string) => Promise<RenderResult>
render: (pagePath: string, helmetContext: object) => Promise<RenderResult>
clientBundle: RollupOutput
routes: Route[]
}) {
Expand All @@ -52,16 +53,20 @@ export async function renderPage({
const renderIndexHTML = (
appHtml: string,
islandsCode: string,
islandProps: unknown[]
islandProps: unknown[],
helmet: HelmetServerState
) =>
`\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>title</title>
<meta name="description" content="xxx">
${helmet?.title?.toString() || ''}
${helmet?.meta?.toString() || ''}
${helmet?.link?.toString() || ''}
${helmet?.style?.toString() || ''}
<meta name="description" content="none">
${styleAssets
.map((item) => `<link rel="stylesheet" href="/${item.fileName}">`)
.join('\n')}
Expand All @@ -85,19 +90,23 @@ export async function renderPage({
console.log(`Rendering page in server side...`)
await Promise.all(
routes.map(async (r) => {
const helmetContext = {
context: {},
} as HelmetData
// 渲染路由对应的页面
const {
appHtml,
islandPathToMap,
islandProps = [],
} = await render(r.path)
} = await render(r.path, helmetContext.context)
const { helmet } = helmetContext.context

// 打包 Islands 组件代码
const islandBundle = await buildIslands(root, islandPathToMap)
const islandsCode = (islandBundle as RollupOutput).output[0].code

// 组件HTML嵌入到模板中
const html = renderIndexHTML(appHtml, islandsCode, islandProps)
const html = renderIndexHTML(appHtml, islandsCode, islandProps, helmet)
// htlm文件名处理
const outputFilePath = r.path.endsWith('/')
? `${r.path}index.html`
Expand Down
23 changes: 21 additions & 2 deletions src/node/plugin-mdx/remarkPlugins/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ export const remarkPluginToc: Plugin<[], Root> = () => {
return (tree) => {
// 初始化 toc 数组
const toc: TocItem[] = []
let title = ''
// 同一个slugger实例具有记忆,重复处理的标题后续的会带上后缀,每次编译时都重新进行实例的初始化
const slugger = new Slugger()
// 遍历 tree
visit(tree, 'heading', (node) => {
// debugger
if (!node.depth || !node.children) return
// 收集 h1 作为 doc.title
if (node.depth === 1) {
title = (node.children[0] as ChildNode).value
}
// 收集 h2~h4 的标题
if (node.depth > 1 && node.depth < 5) {
const text = (node.children as ChildNode[])
Expand Down Expand Up @@ -58,8 +62,23 @@ export const remarkPluginToc: Plugin<[], Root> = () => {
estree: parse(insertCode, {
ecmaVersion: 2020,
sourceType: 'module',
}) as Program,
}) as unknown as Program,
},
} as MdxjsEsm)
// 插入 h1
if (title) {
const insertedTitle = `export const title = '${title}';`

tree.children.unshift({
type: 'mdxjsEsm',
value: insertedTitle,
data: {
estree: parse(insertedTitle, {
ecmaVersion: 2020,
sourceType: 'module',
}) as unknown as Program,
},
} as MdxjsEsm)
}
}
}
2 changes: 2 additions & 0 deletions src/runtime/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function initPageData(routePath: string): Promise<PageData> {
pagePath: routePath,
frontmatter: {},
pageType: '404',
title: '404',
}
}
const moduleInfo = await matched[0].route.preload()
Expand All @@ -29,5 +30,6 @@ export async function initPageData(routePath: string): Promise<PageData> {
frontmatter: moduleInfo.frontmatter,
pageType: moduleInfo.frontmatter?.pageType ?? 'doc',
toc: moduleInfo.toc,
title: moduleInfo.title,
}
}
13 changes: 8 additions & 5 deletions src/runtime/client-entry.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createRoot, hydrateRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { HelmetProvider } from 'react-helmet-async'
import App, { initPageData } from './App'
import { DataContext } from './hooks'
import type { ComponentType } from 'react'
Expand All @@ -22,11 +23,13 @@ async function renderInBrowser() {
const pageData = await initPageData(location.pathname)

createRoot(containerEl).render(
<DataContext.Provider value={pageData}>
<BrowserRouter>
<App />
</BrowserRouter>
</DataContext.Provider>
<HelmetProvider>
<DataContext.Provider value={pageData}>
<BrowserRouter>
<App />
</BrowserRouter>
</DataContext.Provider>
</HelmetProvider>
)
} else {
// 生产环境下的 Partial Hydration
Expand Down
15 changes: 9 additions & 6 deletions src/runtime/ssr-entry.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom/server'
import { HelmetProvider } from 'react-helmet-async'
import App, { initPageData } from './App'
import { DataContext } from './hooks'

Expand All @@ -10,19 +11,21 @@ export interface RenderResult {
}

// For ssr component render
export async function render(pagePath: string) {
export async function render(pagePath: string, helmetContext: object) {
// 生产 pageData
const pageData = await initPageData(pagePath)
// 清除 islands 数据
const { clearIslandData, data } = await import('./jsx-runtime')
clearIslandData()

const appHtml = renderToString(
<DataContext.Provider value={pageData}>
<StaticRouter location={pagePath}>
<App />
</StaticRouter>
</DataContext.Provider>
<HelmetProvider context={helmetContext}>
<DataContext.Provider value={pageData}>
<StaticRouter location={pagePath}>
<App />
</StaticRouter>
</DataContext.Provider>
</HelmetProvider>
)
// 保证每次都能拿到最新的数据
const { islandProps, islandPathToMap } = data
Expand Down
6 changes: 5 additions & 1 deletion src/runtime/theme-default/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Helmet } from 'react-helmet-async'
import { Nav } from '../components/Nav'
import { HomeLayout } from './HomeLayout'
import { DocLayout } from './DocLayout'
Expand All @@ -9,7 +10,7 @@ import 'uno.css'

export function Layout() {
const pageData = usePageData()
const { pageType } = pageData
const { pageType, title } = pageData
const getContent = () => {
if (pageType === 'home') {
return <HomeLayout />
Expand All @@ -21,6 +22,9 @@ export function Layout() {
}
return (
<div>
<Helmet>
<title>{title}</title>
</Helmet>
<Nav />
<section
style={{
Expand Down
2 changes: 2 additions & 0 deletions src/shared/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface PageModule {
frontmatter?: FrontMatter
toc?: Header[]
[key: string]: unknown
title?: string
}

// 用户配置的超集
Expand Down Expand Up @@ -80,6 +81,7 @@ export interface PageData {
frontmatter: FrontMatter
pageType: PageType
toc?: Header[]
title?: string
}

export interface Feature {
Expand Down

0 comments on commit e98f60a

Please sign in to comment.