At the beginning of the New Year, I think everyone here is in the waiting demand (PS: paddling fish), sorry, I am proud to say that I am too. A few days fish touch down, the in the mind is not the taste, looking at the students around every day are learning this learning that, make me not very bashful. So while we are still waiting for all the solutions and frameworks to come out, let’s put together the server side of Vue3.0 and write it for ourselves.
Well, that’s all for now. We started the research based on Webpack and VUe-CLI, but gave up after encountering some problems on the way. Due to uVU’s recent obsession with Vite, vue-CLI related ecology is also lagging behind. So backhand on a hand vite play, see what magic let us especially big thirty night still in the masturbating code.
The beginning of learning is undoubtedly to seeThe document”, coinciding with vite2.0’s launch. I found the one that was easySSR demo.Once you see this sentence, it shows that the next journey is very exciting, can start to play the imagination of the place is very much. No more words, download first, and then according to their own want to modify it.
Open the demo project, the general logic has been written for us almost, the rest of the server prefetch data, store state takeover, etc., we can say that a little transformation can be used for production, very nice.
So let’s talk about the code. Js file, this file is actually to help us start an SSR server.
// @ts-check
const fs = require('fs')
const path = require('path')
const express = require('express')
const serialize = require('serialize-javascript');
const isTest = process.env.NODE_ENV === 'test'| |!!!!! process.env.VITE_TEST_BUILDasync function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === 'production'
) {
const resolve = (p) = > path.resolve(__dirname, p)
const indexProd = isProd
? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
: ' '
const manifest = isProd
? // @ts-ignore
require('./dist/client/ssr-manifest.json')
: {}
const app = express()
/ * * *@type {import('vite').ViteDevServer}* /
let vite
if(! isProd) { vite =await require('vite').createServer({
root,
logLevel: isTest ? 'error' : 'info'.server: {
middlewareMode: true
}
})
app.use(vite.middlewares)
} else {
app.use(require('compression') ())// Put the packed CSS, JS and other files into the static file server
app.use(
require('serve-static')(resolve('dist/client'), {
index: false
})
)
}
app.use(The '*'.async (req, res) => {
try {
const url = req.originalUrl
let template, render
// Read the index.html template file
if(! isProd) {console.log('Current request path', url);
template = fs.readFileSync(resolve('index.html'), 'utf-8')
template = await vite.transformIndexHtml(url, template)
render = (await vite.ssrLoadModule('/src/entry-server.js')).render
} else {
template = indexProd
// @ts-ignore
render = require('./dist/server/entry-server.js').render
}
// Call the server-side rendering method to render the Vue component into a DOM structure, and analyze the js, CSS and other files that need to be preloaded.
const [appHtml, preloadLinks, store] = await render(url, manifest)
// Add + to the server prefetch data store, insert HTML template file
const state = ("<script>window.__INIT_STATE__" + "=" + serialize(store, {isJSON: true}) + "</script>");
// Replace the stand character in the HTML with the corresponding resource file
const html = template
.replace(` <! --preload-links-->`, preloadLinks)
.replace(` <! --app-html-->`, appHtml)
.replace(` <! --app-store-->`, state)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite && vite.ssrFixStacktrace(e)
console.log(e.stack)
res.status(500).end(e.stack)
}
})
return { app, vite }
}
// Create a node server for SSR
if(! isTest) { createServer().then(({ app }) = >
app.listen(3000.() = > {
console.log('http://localhost:3000')}}))// for test use
exports.createServer = createServer
Copy the code
index.html
Is the template file in server.js, you can see that there is the corresponding booth symbol to be replaced
src/main.ts
Because every request that reaches the server requires a new code that is not tainted by the previous request, this file is essentially the factory function of the current running environment, returning a new vue instance, router instance, Store instance, and so on each time.
src/entry-server.js
Server-side render entry functions
import { createApp } from "./main";
import { renderToString } from "@vue/server-renderer";
import { getAsyncData } from '@src/utils/publics';
export async function render(url, manifest) {
const { app, router, store } = createApp();
Url / / synchronization
router.push(url);
store.$setSsrPath(url);
await router.isReady();
When the route is ready, call the custom hook to fetch data from the server
await getAsyncData(router, store, true);
// Generate an HTML string
const ctx = {};
const html = await renderToString(app, ctx);
// Generate the resource prefetch array according to the server prefetch manifest generated during packaging
const preloadLinks = ctx.modules
? renderPreloadLinks(ctx.modules, manifest)
: [];
return[html, preloadLinks, store]; } to omit...Copy the code
SRC /entry-client.js Client rendering entry function
import { createApp } from './main'
const { app, router, store } = createApp()
router.isReady().then(() = > {
// Hang on the dom of the current vue instance whose id is app
app.use(VueRescroll)
.use(VueImageLazyLoad)
.mount('#app');
})
// Enable post-route hooks for page data requests
router.afterEach(() = > {
getAsyncData(router, store, false);
})
Copy the code
GetAsyncData adds the asyncData hook as a data prefetch hook, as vue2.0 does
Execute the register Store hook
export const registerModules = (components: Component[], router: Router, store: BaseStore) = > {
return components
.filter((i: any) = > typeof i.registerModule === "function")
.forEach((component: any) = > {
component.registerModule({ router: router.currentRoute, store });
});
};
// Call the asyncData hook of the currently matched component to prefetch data
export const prefetchData = (components: Component[], router: Router, store: BaseStore) = > {
const asyncDatas: any[] = components.filter(
(i: any) = > typeof i.asyncData === "function"
);
return Promise.all(
asyncDatas.map((i) = > {
return i.asyncData({ router: router.currentRoute.value, store }); })); };// SSR custom hooks
export const getAsyncData = (
router: Router,
store: BaseStore,
isServer: boolean
): Promise<void> = > {return new Promise(async (resolve) => {
const { matched, fullPath } = router.currentRoute.value;
// The component to which the current route matches
const components: Component[] = matched.map((i) = > {
return i.components.default;
});
// Dynamically register store
registerModules(components, router, store);
if(isServer || store.ssrPath ! == fullPath) {// Prefetch data
awaitprefetchData(components, router, store); ! isServer && store.$setSsrPath("");
}
resolve();
});
};
Copy the code
. Vue has the same prefetch data as data and computed data
async asyncData({ store, router }: any) {
if(! store.blog)return;
const { blogDetail } = store.blog;
blogDetail.$assignParams({
id: router.query.id
})
await blogDetail.loadData();
},
Copy the code
For SSR transformation to do the above, there are some project optimization, such as modularization, TS, store registration on demand, and some custom plug-ins, not one way, like students can download the source code or fork in the past to play, the experiment SSR temporarily does not support TSX/JSX writing method. Students who need to communicate are also welcome to communicate in the comments section.
Project warehouse: github.com/Vitaminaq/c… Repository of plug-ins used in the project: github.com/Vitaminaq/p… (If you like, please help yourself. Welcome to join the development.)