Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4f6ab4f

Browse files
committed
using serverPrefetch on ssr
1 parent b51b0a4 commit 4f6ab4f

File tree

6 files changed

+93
-163
lines changed

6 files changed

+93
-163
lines changed

‎src/entry-client.js

Lines changed: 2 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,9 @@
1-
import cookies from "js-cookie";
2-
import Vue from "vue";
31
import { createApp } from "./main";
4-
const { app, router, store } = createApp();
5-
const userAgent = navigator.userAgent;
6-
const isSSRClient =
7-
document.body.getAttribute("data-server-rendered-page") === "true";
8-
document.body.removeAttribute("data-server-rendered-page");
9-
let isSSRClientFirstLoad = isSSRClient;
10-
11-
const loading = isLoading => {
12-
if (isLoading) {
13-
Vue.prototype.$toast.loading({
14-
mask: true,
15-
message: "加载中...",
16-
overlayStyle: {
17-
backgroundColor: "rgba(0, 0, 0, 0.1)"
18-
},
19-
duration: 0
20-
});
21-
} else {
22-
Vue.prototype.$toast.clear();
23-
}
24-
};
25-
26-
// a global mixin that calls `asyncData` when a route component's params change
27-
Vue.mixin({
28-
beforeMount() {
29-
asyncDataPromise(this, this.$route);
30-
},
31-
beforeRouteUpdate(to, from, next) {
32-
isSSRClientFirstLoad = false;
33-
asyncDataPromise(this, to, true);
34-
next();
35-
}
36-
});
2+
const { app, store } = createApp();
373

384
// prime the store with server-initialized state.
395
// the state is determined during SSR and inlined in the page markup.
406
if (window.__INITIAL_STATE__) {
417
store.replaceState(window.__INITIAL_STATE__);
428
}
43-
44-
router.onReady(() => {
45-
if (!isSSRClient) {
46-
checkIsNeedLoading(router.getMatchedComponents(), router.currentRoute);
47-
}
48-
49-
// Add router hook for handling asyncData before router enter.
50-
router.beforeResolve((to, from, next) => {
51-
isSSRClientFirstLoad = false;
52-
beforeResolveHandle(to, from);
53-
next();
54-
});
55-
app.$mount("#app");
56-
});
57-
58-
function beforeResolveHandle(to, from) {
59-
const matched = router.getMatchedComponents(to);
60-
const prevMatched = router.getMatchedComponents(from);
61-
let diffed = false;
62-
const activated = matched.filter((c, i) => {
63-
return diffed || (diffed = prevMatched[i] !== c);
64-
});
65-
checkIsNeedLoading(activated);
66-
}
67-
68-
function checkIsNeedLoading(matchedComponents) {
69-
const asyncDataHooks = matchedComponents.map(c => c.asyncData).filter(_ => _);
70-
if (!asyncDataHooks.length) return;
71-
loading(true);
72-
}
73-
74-
function asyncDataPromise(vm, route, isNeedLoading) {
75-
const { asyncData } = vm.$options;
76-
if (asyncData) {
77-
// 将获取数据操作分配给 promise
78-
// 以便在组件中,我们可以在数据准备就绪后
79-
// 通过运行 `this.dataPromise.then(...)` 来执行其他任务
80-
vm.dataPromise = null;
81-
if (!isSSRClientFirstLoad) {
82-
isNeedLoading && loading(true);
83-
vm.dataPromise = asyncData({
84-
store: vm.$store,
85-
route: route,
86-
cookies: cookies.get(),
87-
userAgent
88-
});
89-
} else {
90-
vm.dataPromise = Promise.resolve();
91-
}
92-
vm.dataPromise.finally(() => {
93-
loading(false);
94-
});
95-
}
96-
}
9+
app.$mount("#app");

‎src/entry-server.js

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,53 +9,21 @@ export default context => {
99
return new Promise((resolve, reject) => {
1010
const beginTime = Date.now();
1111
const { app, router, store } = createApp();
12-
const { url, cookies, userAgent } = context;
13-
14-
const { fullPath } = router.resolve(url).route;
15-
if (fullPath !== url) {
16-
/* eslint-disable-next-line */
17-
return reject({ url: fullPath });
18-
}
19-
2012
// set router's location
21-
router.push(url);
22-
// wait until router has resolved possible async hooks
13+
router.push(context.url);
2314
router.onReady(() => {
24-
const matchedComponents = router.getMatchedComponents();
25-
// no matched routes
26-
if (!matchedComponents.length) {
15+
// This `rendered` hook is called when the app has finished rendering
16+
context.rendered = () => {
17+
// After the app is rendered, our store is now
18+
// filled with the state from our components.
19+
// When we attach the state to the context, and the `template` option
20+
// is used for the renderer, the state will automatically be
21+
// serialized and injected into the HTML as `window.__INITIAL_STATE__`.
22+
context.state = store.state;
2723
/* eslint-disable-next-line */
28-
return reject({ code: 404 });
29-
}
30-
// Call fetchData hooks on components matched by the route.
31-
// A preFetch hook dispatches a store action and returns a Promise,
32-
// which is resolved when the action is complete and store state has been
33-
// updated.
34-
Promise.all(
35-
matchedComponents.map(
36-
({ asyncData }) =>
37-
asyncData &&
38-
asyncData({
39-
store,
40-
route: router.currentRoute,
41-
cookies,
42-
userAgent
43-
})
44-
)
45-
)
46-
.then(() => {
47-
/* eslint-disable-next-line */
48-
console.log(`[DATE] data pre-fetch: ${Date.now() - beginTime}ms url=${fullPath}`);
49-
// After all preFetch hooks are resolved, our store is now
50-
// filled with the state needed to render the app.
51-
// Expose the state on the render context, and let the request handler
52-
// inline the state in the HTML response. This allows the client-side
53-
// store to pick-up the server-side state without having to duplicate
54-
// the initial data fetching on the client.
55-
context.state = store.state;
56-
resolve(app);
57-
})
58-
.catch(reject);
24+
console.log(`[DATE] data pre-fetch: ${Date.now() - beginTime}ms url=${context.url}`);
25+
};
26+
resolve(app);
5927
}, reject);
6028
});
6129
};

‎src/main.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ Vue.config.productionTip = true;
1212
import Vant from "vant";
1313
import "vant/lib/index.css";
1414
Vue.use(Vant);
15+
// global loading
16+
Vue.prototype.$loading = isLoading => {
17+
if (isLoading) {
18+
Vue.prototype.$toast.loading({
19+
mask: true,
20+
message: "加载中...",
21+
overlayStyle: {
22+
backgroundColor: "rgba(0, 0, 0, 0.1)"
23+
},
24+
duration: 0
25+
});
26+
} else {
27+
Vue.prototype.$toast.clear();
28+
}
29+
};
1530

1631
export function createApp() {
1732
const router = createRouter();
@@ -26,3 +41,17 @@ export function createApp() {
2641
});
2742
return { app, router, store };
2843
}
44+
45+
// promise.finally Polyfill
46+
if (!Promise.prototype.finally) {
47+
Promise.prototype.finally = function(callback) {
48+
let P = this.constructor;
49+
return this.then(
50+
value => P.resolve(callback()).then(() => value),
51+
reason =>
52+
P.resolve(callback()).then(() => {
53+
throw reason;
54+
})
55+
);
56+
};
57+
}

‎src/views/Detail.vue

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export default {
1717
name: "Detail",
1818
data() {
1919
return {
20-
pageIndex: -1
20+
pageIndex: -1,
21+
isMounted: false //for control not ssr render
2122
};
2223
},
2324
computed: {
@@ -28,23 +29,43 @@ export default {
2829
title: this.topicDetail && this.topicDetail.title
2930
};
3031
},
31-
// eslint-disable-next-line
32-
asyncData({ store, route: { params, query, fullPath }, cookies, userAgent }) {
33-
return store.dispatch("FETCH_TOPIC_DETAIL", { id: params.id });
32+
serverPrefetch() {
33+
return this.fetchData();
3434
},
3535
mounted() {
36-
this.dataPromiseCallBack();
36+
this.isMounted = true;
37+
const alreadyIncremented = !!this.topicDetail;
38+
if (!alreadyIncremented) {
39+
this.fetchData().then(this.fetchDataMounted());
40+
} else {
41+
this.fetchDataMounted();
42+
}
3743
},
3844
beforeRouteUpdate(to, from, next) {
3945
// 一般建议路由变更采用计算属性或者store直接绑定
4046
// 特殊情况处理可以采用如下方案 重新注册数据返回处理
41-
this.dataPromiseCallBack();
47+
this.fetchData().then(this.fetchDataMounted());
4248
next();
4349
},
4450
destroyed() {
4551
this.$store.commit("SET_TOPIC_DETAIL", { detail: null });
4652
},
4753
methods: {
54+
// fetchData for client and server render
55+
fetchData() {
56+
this.isMounted && this.$loading(true);
57+
return this.$store
58+
.dispatch("FETCH_TOPIC_DETAIL", {
59+
id: this.$route.params.id
60+
})
61+
.finally(() => {
62+
this.$loading(false);
63+
});
64+
},
65+
// fetchData callback on mounted
66+
fetchDataMounted() {
67+
this.pageIndex = this._getCurrentIndexInList();
68+
},
4869
next() {
4970
const itemIndex = this._getCurrentIndexInList();
5071
if (itemIndex > -1 && itemIndex < this.topicsList.length - 1) {
@@ -59,12 +80,6 @@ export default {
5980
this.$router.push({ path: `/detail/${id}` });
6081
}
6182
},
62-
dataPromiseCallBack() {
63-
// 注册数据回调处理
64-
this.dataPromise.then(() => {
65-
this.pageIndex = this._getCurrentIndexInList();
66-
});
67-
},
6883
_getCurrentIndexInList() {
6984
if (!this.topicsList) return undefined;
7085
const finds = this.topicsList.filter(i => i.id == this.$route.params.id);

‎src/views/Home.vue

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
<script>
1717
import { mapState } from "vuex";
1818
import TopicItem from "../components/TopicItem";
19-
// @ is an alias to /src
2019
export default {
2120
name: "home",
2221
components: { TopicItem },
@@ -38,21 +37,36 @@ export default {
3837
ssrHeadAddInfo: `<link rel="canonical" href="https://www.github.com">`
3938
};
4039
},
41-
// eslint-disable-next-line
42-
asyncData({ store, route: { params, query, fullPath }, cookies, userAgent }) {
43-
return store.dispatch("FETCH_TOPICS_LIST", { cookies });
40+
serverPrefetch() {
41+
return this.fetchData();
4442
},
4543
mounted() {
4644
this.isMounted = true;
47-
// 注册数据回调处理,仅限mounted后面生命周期中使用
48-
this.dataPromise.then(() => {
45+
const alreadyIncremented = !!this.topicsList;
46+
if (!alreadyIncremented) {
47+
this.fetchData().then(this.fetchDataMounted());
48+
} else {
49+
this.fetchDataMounted();
50+
}
51+
},
52+
methods: {
53+
// fetchData for client and server render
54+
fetchData() {
55+
this.isMounted && this.$loading(true);
56+
const cookies = this.$ssrContext && this.$ssrContext.cookies;
57+
return this.$store
58+
.dispatch("FETCH_TOPICS_LIST", { cookies })
59+
.finally(() => {
60+
this.$loading(false);
61+
});
62+
},
63+
// fetchData callback on mounted
64+
fetchDataMounted() {
4965
this.topicsList &&
5066
this.topicsList.forEach(item => {
5167
item.create_at = new Date(item.create_at).toDateString();
5268
});
53-
});
54-
},
55-
methods: {
69+
},
5670
navDetail(detail) {
5771
this.$router.push({ path: `/detail/${detail.id}` });
5872
}

‎vue.config.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,6 @@ module.exports = {
106106
});
107107

108108
if (TARGET_NODE) {
109-
// 优化ssr loader
110-
config.module
111-
.rule("vue")
112-
.use("vue-loader")
113-
.tap(args => {
114-
args.optimizeSSR = true;
115-
return args;
116-
});
117-
118109
// fix ssr bug: document not found -- https://github.com/Akryum/vue-cli-plugin-ssr/blob/master/lib/webpack.js
119110
const isExtracting = config.plugins.has("extract-css");
120111
if (isExtracting) {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /