Skip to content
0

SSG

SSG 全称为 Static Site Generate,静态站点生成

SSR 中的一小节有提到,当不同的路由请求发送到 express 服务的时候,本质上命中的是通配符 *,然后根据不同的 url 渲染页面:

async function render(url, manifest) {
  const { app, router } = createApp()
  await router.push(url)
  await router.isReady()

  const ctx = {}

  const html = renderToString(app, ctx)
}

那么,如果每次返回的内容相对是静态的,我们似乎可以提前进行渲染,除了 index.html 之外,生成多个路由对应的 HTML 文件

假设我们的项目中有如下多个页面(例如使用了 vite-plugin-page 插件),如下面的 pages 文件夹:

  • pages
    • home.vue
    • about.vue
    • external.vue
    • foo.vue
  • vite.config.ts
  • package.json

我们就可以写一个 prerender.js 脚本:

const toAbsolute = p => path.resolve(__dirname, p)

const manifest = JSON.parse(
  fs.readFileSync(toAbsolute('dist/static/.vite/ssr-manifest.json'), 'utf8'),
)
const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf8')
const { render } = await import('./dist/server/entry-server.js')

const routesToPrerender = fs
  .readdirSync(toAbsolute('src/pages'))
  .map(file => {
    const name = file.replace(/\.vue/, '').toLowerCase()
    return name === 'home' ? `/` : `/${name}`
  })

;(async () => {
  for(const url of routesToPrerender) {
    const [appHtml, preloadLinks] = await render(url, manifest)

    const html = template
      .replace('<!--app-html-->', appHtml)

    const filePath = `dist/static${url === '/' ? 'index.html/' : url}.html`
    fs.writeFileSync(filePath, html)
  }

  fs.rmSync(toAbsolute('dist/static/.vite'), { recursive: true })
})()

所以 SSG 的做法可以理解为,在 SSR 打包之后执行一个额外的生成脚本,进行预渲染

{
  "scripts": {
    "build:client": "vite build --ssrManifest .vite/ssr-manifest.json --outDir dist/client",
    "build:static": "vite build --ssrManifest .vite/ssr-manifest.json --outDir dist/static",
    "build:server": "vite build --ssr src/entry-server.js --outDir dist/server",
    "ssr": "npm run build:client && npm run build:server",
    "ssg": "npm run build:static && npm run build:server && node ./prerender.js"
  }
}

Released under the MIT License.