UNPKG

flow-vue-ssr-hook

Version:

flow-build针对vue服务端渲染的解决方案

183 lines (159 loc) 5.13 kB
import Vue from "vue"; import Meta from "vue-meta"; import root from "@/app"; import { createRedirect, promisify } from "./utils"; const isDev = process.env.NODE_ENV !== "production"; if (!root.data || typeof root.data != "function") { throw new Error( "The app.js file must have a data method, and the data must be a function" ); } // vue-meta configuration Vue.use(Meta, { keyName: "head", // the component option name that vue-meta looks for meta info on. attribute: "data-n-head", // the attribute name vue-meta adds to the tags it observes ssrAttribute: "data-n-head-ssr", // the attribute name that lets vue-meta know that meta info has already been server-rendered tagIDKeyName: "hid" // the property name that vue-meta uses to determine whether to overwrite or append a tag }); const { app, router, store } = root.data(); // prime the store with server-initialized state. // the state is determined during SSR and inlined in the page markup. if (window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__); } let browserDataStatus = false; let dealBrowserData = (vm, to, from) => { if (browserDataStatus) return false; const { browserData } = vm.$options; if (browserData) { browserDataStatus = true; let promise = browserData.call(vm, { ssr: store.state.SSR_FETCHED, to: to, from: from }); if ( !promise || (!(promise instanceof Promise) && typeof promise.then !== "function") ) { promise = Promise.resolve(promise); } return promise .then(r => { browserDataStatus = false; return r; }) .catch(e => { browserDataStatus = false; return e; }); } else { return Promise.resolve(); } }; Vue.mixin({ beforeRouteUpdate(to, from, next) { dealBrowserData(this, to, from); next(); }, mounted() { dealBrowserData(this); }, activated() { dealBrowserData(this); } }); function render(to, from, next) { let context = { _status: { redirected: false }, params: to.params, query: to.query, next: next }; context.redirect = createRedirect(context, router, false); if (store && store.state.SSR_FETCHED) { return next(); } const matched = router.getMatchedComponents(to); let isValid = true; matched.forEach(component => { if (!isValid) return; if (typeof component.validate !== "function") return; isValid = component.validate({ params: to.params || {}, query: to.query || {} }); }); if (!isValid) { context.redirect("/404"); return next(); } const asyncDataHooks = matched.map(c => c.asyncData).filter(_ => _); if (!asyncDataHooks.length) { return next(); } if (root.methods && typeof root.methods.asyncDataBefore === "function") { root.methods.asyncDataBefore(); } Promise.all( asyncDataHooks.map(asyncData => { if (typeof asyncData === "function") { return promisify(asyncData, { store, route: to, context }).catch(err => { context.redirect(err.url || "/404"); return Promise.resolve(err); }); } else if ( Object.prototype.toString.call(asyncData) === "[object Object]" ) { if (typeof asyncData.type === "string") { return store .dispatch(asyncData.type, context) .catch(err => { context.redirect(asyncData.redirect || "/404"); return Promise.resolve(err); }); } else { if (isDev) { throw new Error( "The type field must be string type, if asyncData is an object" ); } return Promise.resolve(false); } } else { return Promise.resolve(false); } }) ) .then(() => { if ( root.methods && typeof root.methods.asyncDataComplete === "function" ) { root.methods.asyncDataComplete(); } if (context._status.redirected) { next(false); } else { next(); } }) .catch(next); } // wait until router has resolved all async before hooks // and async components... router.onReady(() => { router.beforeResolve(render); // actually mount to DOM app.$mount(root.el || "#app"); Vue.nextTick(() => { store.state.SSR_FETCHED = false; }); });