本地 JSON

内容以 JSON 的形式放在仓库里,由 sitelo/data 变成静态页面。没有 API,没有数据库,也没有客户端 JavaScript。完整源码见 examples/json.

你会得到

项目结构

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 { 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)),
        ),
      ),
    ),
  )

4. 每个 JSON 文件一个页面

generateStaticParams 在构建时为每个文件返回一个 slug;data() 则为每个页面加载对应的条目。

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 ? '有货' : '缺货'),
      ),
    ),
  )

5. 边改边看

npm install
npm run build

sitelo 下,改一个价格就会刷新打开着的页面 —— 开发服务器只监听页面真正读取过的 JSON 文件。slug 重复、文件缺失和 JSON 格式错误都会让构建失败,并指出出问题的路径。

数据加载文档 · 路由文档 · 配置文档