SSR学习笔记(五) manifest资源预加载

444 阅读1分钟

预加载占位符 index.html

<title>Vite App</title>
<!--preload-link-->

客户端打包manifest文件

"scripts": {
  xxxxx
  "build:client": "vite build --outDir dist/client --ssrManifest",
  xxxxx
},

线上环境预加载资源 server.js

     if (isProd) {
        xxxxx

        const manifest = require('./dist/client/ssr-manifest.json')
        const { appHtml, state, preloadLinks } = await render(url, manifest)
        html = template
          .replace('<!--preload-link-->', preloadLinks)
          .replace(`<!--ssr-outlet-->`, appHtml)
          .replace('\'<!--vuex-state-->\'', JSON.stringify(state))
      } else {
        xxxxx

        const { appHtml, state } = await render(url)
        html = template
          .replace(`<!--ssr-outlet-->`, appHtml)
          .replace('\'<!--vuex-state-->\'', JSON.stringify(state))
      }

服务端入口处理预加载资源 src/entry-server.js

xxxxx

export async function render(url, manifest) {
  xxxxx

  const context = {}
  const appHtml = await renderToString(app, context)
  const state = store.state
  if (import.meta.env.PROD) {
    const preloadLinks = renderLinks(context.modules, manifest)
    return { appHtml, state, preloadLinks }
  } else {
    return { appHtml, state }
  }
}

const renderLinks = (modules, manifest) => {
  let links = ''
  modules.forEach(id => {
    const files = manifest[id]
    if (files) {
      files.forEach(file => {
        links += renderPreloadLink(file)
      })
    }
  });
  return links
}
const renderPreloadLink = (file) => {
  if (file.endsWith('.js')) {
    return `<link rel="modulepreload" crossorigin href="${file}">`
  } else if (file.endsWith('.css')) {
    return `<link rel="stylesheet" href="${file}">`
  } else if (file.endsWith('.woff')) {
    return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
  } else if (file.endsWith('.woff2')) {
    return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
  } else if (file.endsWith('.gif')) {
    return ` <link rel="preload" href="${file}" as="image" type="image/gif">`
  } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
    return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`
  } else if (file.endsWith('.png')) {
    return ` <link rel="preload" href="${file}" as="image" type="image/png">`
  } else {
    // TODO
    return ''
  }
}

防止数据二次预取 src/entry-client.js

xxxxx

router.isReady().then(() => {
  // 防止数据二次预取
  router.beforeResolve((to, from, next) => {
    let toComponent = router.resolve(to).matched.flatMap(record =>
      Object.values(record.components)
    )
    let fromComponent = router.resolve(from).matched.flatMap(record =>
      Object.values(record.components)
    )
    let actived = toComponent.filter(c => {
      return fromComponent !== c
    })
    if (!actived.length) {
      return next()
    }
    console.log('开始loading')
    Promise.all(actived.map(Component => {
      if (Component.asyncData) {
        return Component.asyncData({
          store,
          route: router.currentRoute
        })
      }
    })).then(() => {
      console.log('结束loading')
      next()
    })
  })
  app.mount('#app')
})