预加载占位符 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 {
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')
})