本地 JSON
本页内容
内容以 JSON 的形式放在仓库里,由 sitelo/data 变成静态页面。没有 API,没有数据库,也没有客户端 JavaScript。完整源码见 examples/json.
你会得到
- 一个列出全部分类和产品的首页
/products/[slug]——data/products/中的每个文件对应一个静态页面/categories/[slug]——data/categories.json中的每个键对应一个页面- 新增一个 JSON 文件就等于新增一个页面,无需注册路由
- 不向浏览器发送任何 JS —— 文件在构建时于 Node 中读取
项目结构
my-site/
sitelo.config.js
data/
site.json # 全站设置
categories.json # 一个对象,以 slug 为键
products/ # 每个产品一个文件
aeron-chair.json
jarvis-desk.json
tolomeo-lamp.json
src/
lib/
catalogue.js # 唯一接触 sitelo/data 的模块
index.ht.js # / —— 分类和全部产品
products/
[slug].ht.js # /products/:slug —— 每个 JSON 文件一个页面
categories/
[slug].ht.js # /categories/:slug —— 每个键一个页面数据放在 src/ 之外,因此 sitelo 不会把它们当成页面或资源。
1. 把内容放进 data/
每个产品一个文件。文件名就是 slug,因此 aeron-chair.json 就是 /products/aeron-chair,文件里不必再声明一次:
{
"name": "Aeron Chair",
"category": "seating",
"price": 1395,
"released": "2022-04-01",
"summary": "Mesh task chair with adjustable lumbar support.",
"inStock": true
}分类则放在单个文件里:一个以 slug 为键的对象,readJsonCollection 同样会把它当作集合来读。
{
"seating": {
"name": "Seating",
"description": "Chairs and stools for long working days."
},
"desks": {
"name": "Desks",
"description": "Fixed-height and sit-stand work surfaces."
}
}2. 在一处读取
一个仅在服务端运行的小模块封装了这些读取。HTML 中没有任何地方引用它,所以它永远不会进入浏览器 —— 而且因为 sitelo/data 按文件记忆结果,无论多少页面调用这些辅助函数,整个构建过程中每个 JSON 文件都只解析一次。
import { readJson, readJsonCollection } from 'sitelo/data'
/** 全站设置:一个 JSON 文件,原样解析。 */
export function getSite() {
return readJson('data/site.json')
}
/** 每个文件一条记录,slug 取自文件名。 */
export function getProducts() {
return readJsonCollection('data/products', { sort: 'name' })
}
/** 一个文件,内容是以 slug 为键的对象 —— 键即 slug。 */
export function getCategories() {
return readJsonCollection('data/categories.json')
}
/** 某个分类下的产品,最便宜的排在前面。 */
export async function getProductsInCategory(slug) {
const products = await readJsonCollection('data/products', { sort: 'price' })
return products.filter((product) => product.category === slug)
}3. 在首页列出全部内容
import { getCategories, getProducts, getSite } from './lib/catalogue.js'
export async function data() {
return {
site: await getSite(),
categories: await getCategories(),
products: await getProducts(),
}
}
export default ({ data }) => `
<html lang="zh">
<head>
<title>${data.site.title}</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>${data.site.title}</h1>
<h2>分类</h2>
<ul>
${data.categories
.map(
(category) =>
`<li><a href="/categories/${category.slug}">${category.name}</a></li>`,
)
.join('')}
</ul>
<h2>全部产品</h2>
<ul>
${data.products
.map(
(product) =>
`<li><a href="/products/${product.slug}">${product.name}</a></li>`,
)
.join('')}
</ul>
</body>
</html>
`import { html, head, title, link, body, h1, h2, ul, li, a } from 'javascript-to-html'
import { getCategories, getProducts, getSite } from './lib/catalogue.js'
export async function data() {
return {
site: await getSite(),
categories: await getCategories(),
products: await getProducts(),
}
}
export default ({ data }) =>
html({ lang: 'zh' },
head(
title(data.site.title),
link({ rel: 'stylesheet', href: '/css/styles.css' }),
),
body(
h1(data.site.title),
h2('分类'),
ul(
...data.categories.map((category) =>
li(a({ href: `/categories/${category.slug}` }, category.name)),
),
),
h2('全部产品'),
ul(
...data.products.map((product) =>
li(a({ href: `/products/${product.slug}` }, product.name)),
),
),
),
)import { getCategories, getProducts, getSite } from './lib/catalogue.js'
export async function data() {
return {
site: await getSite(),
categories: await getCategories(),
products: await getProducts(),
}
}
export default function Home({ data }) {
return (
<html lang="zh">
<head>
<title>{data.site.title}</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
<h1>{data.site.title}</h1>
<h2>分类</h2>
<ul>
{data.categories.map((category) => (
<li key={category.slug}>
<a href={`/categories/${category.slug}`}>{category.name}</a>
</li>
))}
</ul>
<h2>全部产品</h2>
<ul>
{data.products.map((product) => (
<li key={product.slug}>
<a href={`/products/${product.slug}`}>{product.name}</a>
</li>
))}
</ul>
</body>
</html>
)
}4. 每个 JSON 文件一个页面
generateStaticParams 在构建时为每个文件返回一个 slug;data() 则为每个页面加载对应的条目。
import { getProduct, getProducts } from '../lib/catalogue.js'
export async function generateStaticParams() {
const products = await getProducts()
return products.map((product) => ({ slug: product.slug }))
}
export async function data({ params }) {
const product = await getProduct(params.slug)
if (!product) throw new Error(`Unknown product: ${params.slug}`)
return { product }
}
export default ({ data }) => `
<html lang="zh">
<head>
<title>${data.product.name}</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>${data.product.name}</h1>
<p>${data.product.summary}</p>
<dl>
<dt>分类</dt>
<dd>${data.product.category}</dd>
<dt>发布日期</dt>
<dd>${data.product.released}</dd>
<dt>库存</dt>
<dd>${data.product.inStock ? '有货' : '缺货'}</dd>
</dl>
</body>
</html>
`import { html, head, title, link, body, h1, p, dl, dt, dd } from 'javascript-to-html'
import { getProduct, getProducts } from '../lib/catalogue.js'
export async function generateStaticParams() {
const products = await getProducts()
return products.map((product) => ({ slug: product.slug }))
}
export async function data({ params }) {
const product = await getProduct(params.slug)
if (!product) throw new Error(`Unknown product: ${params.slug}`)
return { product }
}
export default ({ data }) =>
html({ lang: 'zh' },
head(
title(data.product.name),
link({ rel: 'stylesheet', href: '/css/styles.css' }),
),
body(
h1(data.product.name),
p(data.product.summary),
dl(
dt('分类'),
dd(data.product.category),
dt('发布日期'),
dd(data.product.released),
dt('库存'),
dd(data.product.inStock ? '有货' : '缺货'),
),
),
)import { getProduct, getProducts } from '../lib/catalogue.js'
export async function generateStaticParams() {
const products = await getProducts()
return products.map((product) => ({ slug: product.slug }))
}
export async function data({ params }) {
const product = await getProduct(params.slug)
if (!product) throw new Error(`Unknown product: ${params.slug}`)
return { product }
}
export default function Product({ data }) {
return (
<html lang="zh">
<head>
<title>{data.product.name}</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
<h1>{data.product.name}</h1>
<p>{data.product.summary}</p>
<dl>
<dt>分类</dt>
<dd>{data.product.category}</dd>
<dt>发布日期</dt>
<dd>{data.product.released}</dd>
<dt>库存</dt>
<dd>{data.product.inStock ? '有货' : '缺货'}</dd>
</dl>
</body>
</html>
)
}5. 边改边看
npm install
npm run build在 sitelo 下,改一个价格就会刷新打开着的页面 —— 开发服务器只监听页面真正读取过的 JSON 文件。slug 重复、文件缺失和 JSON 格式错误都会让构建失败,并指出出问题的路径。