// Bar.vue<template> <div class='bar'>
Shows the final rendering result. For the source code, see here.
Step 2: backend rendering (excluding Ajax data)
Although the Demo in the first step does not contain any Ajax data, it is not easy to transform it into backend rendering. Which of the following aspects should we start from?
- Split JS entry;
- Split the Webpack package configuration;
- Compile the server rendering subject logic.
1. Split JS entry
During front-end rendering, only one entry app. js is required. Now to do back-end rendering, you have to have two JS files: entry-client.js and entry-server.js respectively as the browser and server entry.
First look at the entry-client.js, it with the first step of app. js what is the difference? → There is no difference. I just changed the name and the content is the same.
Looking at the entry-server.js, it just returns the instance of App. vue.
// entry-server.jsexport default function createApp() { const app = new Vue({ render: h => h(App) }); return app; };
The main differences between entry-server.js and entry-client.js are as follows:
- The entry-client.js is executed on the browser side, so you need to specify el and explicitly call the $ mount method to start rendering of the browser.
- The entry-server.js is called on the server and therefore needs to be exported as a function.
2. Split the Webpack package Configuration
In the first step, because only app. js has one portal, only one Webpack configuration file is required. Now there are two portals. Naturally, we need two Webpack configuration files: webpack. server. conf. js and webpack. client. conf. js, and their public part is abstracted as webpack. base. conf. js.
There are two notes for webpack. server. conf. js:
- LibraryTarget: 'commonjs2 '→ because the server is a Node, it must be packaged according to commonjs specifications before it can be called by the server.
- Target: 'node' → specify the node environment to avoid non-Node environment-specific API errors, such as document.
3. Compile the server rendering subject Logic
Vue SSR depends on the package vue-server-render. Its calling supports two entry formats: createRenderer and createBundleRenderer. The former uses the Vue component as the entry, the latter takes the packaged JS file as the entry point. This article uses the latter.
// Server. js server rendering subject logic // dist/server. js is the JS const bundle = fs packaged with the entry-server.js as the entrance. readFileSync (path. resolve (_ dirname, 'dist/server. js '), 'utf-8'); const renderer = require ('vue-server-renderer '). createBundleRenderer (bundle, {template: fs. readFileSync (path. resolve (_ dirname, 'dist/index.ssr.html '), 'utf-8')}); server. get ('/Index', (req, res) => {renderer. renderToString (err, html) =>{ if (err) {console. error (err); res. status (500 ). end ('internal server error'); return;} res. end (html) ;}}); server. listen (8002, () => {console. log ('start the backend rendering server, Port: 123456 ');});
The final rendering effect of this step is shown in. We can see that the component has been successfully rendered by the backend. For the source code, see here.
Step 3: backend rendering (pre-obtaining Ajax data)
This is a key step and the most difficult one.
What if the components in step 2 need to request Ajax data? The official documents give us some ideas. I will briefly summarize them as follows:
- Obtain all the required Ajax data (which is stored in the Vuex Store) in advance before rendering );
- During backend rendering, the obtained Ajax data is injected into each component through Vuex;
- Bury all Ajax data in window. INITIAL_STATE and pass it to the browser through HTML;
- The browser injects Ajax data in window. INITIAL_STATE into each component through Vuex.
The following are the highlights of the interview.
We know that in conventional Vue frontend rendering, component request Ajax is generally written as follows: "calling this in mounted. fetchData, and then write the returned data to the data of the instance in the callback. this is OK."
In SSR, this is not feasible because the server does not execute the mounted cycle. Can we set this. fetchData
Are you sure you want to perform this operation in the created or beforeCreate lifecycles? No. The reason is: this. fetchData is an asynchronous request. After the request is sent, the backend is rendered completely without waiting for the data to be returned, and the Ajax returned data cannot be rendered together.
Therefore, we need to know in advance which components have Ajax requests, and then start component rendering after all these Ajax requests are returned.
// Store. jsfunction fetchBar () {return new Promise (function (resolve, reject) {resolve ('bar ajax return data') ;});} export default function createStore () {return new Vuex. store ({state: {bar: '',}, actions: {fetchBar ({commit}) {return fetchBar (). then (msg =>{ commit ('setbar', {msg}) }}, mutations: {setBar (state, {msg}) {Vue. set (state, 'bar', msg );}}})}
// Bar.uveasyncData({store}) { return store.dispatch('fetchBar');},computed: { bar() { return this.$store.state.bar; }}
The asyncData method of the component has been defined, but how can I index this asyncData method? First, let's see how the root component App. vue is written.
// App.vue<template> <div>
From the root component App. vue, we can find the asyncData method of each component by parsing its components field.
// Entry-server.js export default function (context) {// context is the parameter const store = createStore (); let app = new vue ({store, render: h => h (App)}); // find all asyncData Methods let components = App. components; let prefetchFns = []; for (let key in components) {if (! Components. hasOwnProperty (key) continue; let component = components [key]; if (component. asyncData) {prefetchFns. push (component. asyncData ({store})} return Promise. all (prefetchFns ). then (res) => {// After Ajax of all components is returned, the app is finally returned for rendering context. state = store. state; // context. what is the state value, window. _ INITIAL_STATE _ is what return app ;});};
There are several interesting questions:
1. Do I have to use vue-router? → No. Although vue-router is used in the official Demo, it is only because the official Demo contains the SPA of multiple pages. Generally, vue-router is required because different routes correspond to different components, and asyncData of all components is not executed every time. But there are exceptions. For example, my old project has only one page (a page contains many components), so vue-router is not required at all, and SSR can also be used. The main difference is how to find the asyncData method to be executed: the official Demo uses vue-router, and I parse the components field directly.
2. Do I have to use Vuex? → Yes, but no. Please refer to the answer below. Why is there a Vuex-like existence required? Let's analyze it.
2.1. After the pre-obtained Ajax data is returned, the Vue component has not started rendering. So we need to put Ajax somewhere first.
2.2. When the Vue component starts rendering, Ajax data must be taken out and correctly transmitted to each component.
2.3. During browser rendering, the window. INITIAL_STATE must be correctly parsed and passed to each component.
Therefore, we have to have a place independent of a view to store, manage, and transmit data. This is why Vuex exists.
3. the backend has converted Ajax data into HTML. Why do I need to transmit Ajax data to the front end through window. INITIAL_STATE? →Because the data still needs to be known during frontend rendering. For example, you have written a component, bound it with a click event, and printed the this. msg field value when you click it. Now the backend renders the component HTML, but the event binding must be completed by the browser. If the browser cannot obtain the same data as the server, where can I find the msg field when a component click event is triggered?
So far, we have completed the backend rendering with Ajax data. This step is the most complex and critical, and requires repeated thinking and attempts. The specific rendering is as follows. For the source code, see here.
Effect
Are you done? Not yet. People say that SSR can increase the rendering speed of the first screen. let's compare it to see if it is true. (Also in the Fast 3G network ).
Official ideas
Now, the Vue SSR Demo is over. The following are some of the changes I made based on the characteristics of my project. readers who are not interested can not read them.
Step 3 are there any shortcomings in the official thinking? I think there are: for old projects, the transformation costs are relatively large. When vuex needs to be explicitly introduced, the set of action and mutations must be adopted, regardless of the amount of code changes or the learning cost of new users.
Is there any way to reduce the number of changes to the old front-end rendering project? I did this.
// Store. js // action, mutations, which are not required, only define an empty stateexport default function createStore () {return new Vuex. store ({state :{}})} // Bar. vue // tagName is the name of the component instance, such as bar1, bar2, foo1, etc., injected by the entry-server.js export default {prefetchData: function (tagName) {return new Promise (resolve, reject) ==>{ resolve ({tagName, data: 'bar ajax data '});})}}
// Entry-server.jsreturn Promise. all (prefetchFns ). then (res) => {// after obtaining the Ajax data, manually write the data to the state. If the data does not pass the action, the key value inside the set of // state is differentiated by tagName, for example, res such as bar1, bar2, and foo1. forEach (item, key) => {Vue. set (store. state, '$ {item. tagName} ', item. data) ;}); context. state = store. state; return app ;});
// Ssrmixin. js // abstracts the computed required by each component into a mixin, and then injects export default {computed: {prefetchData () {let componentTag = this. $ options. _ componentTag; // bar1, bar2, foo1 return this. $ store. state [componentTag] ;}}
So far, we have obtained a variant of the Vue SSR. For component developers, you only need to abstract the original this. fetchData method to the prefetchData method, and then you can use {prefetchData} in the DOM to get the data. For this part of code, see here.
Summary
Vue SSR is indeed an interesting thing. The key is flexible application. This Demo has another legacy problem that has not been solved: When Ajax is abstracted to prefetchData and made into SSR, the original front-end rendering will become invalid. Can I use the same code to support both front-end rendering and back-end rendering? In this way, when there is a problem with the backend rendering, I can switch back to the front-end rendering at any time, so I have a full-disclosure solution.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.