flow-vue-ssr-hook
Version:
flow-build针对vue服务端渲染的解决方案
151 lines (133 loc) • 5.43 kB
JavaScript
import Vue from "vue";
import Meta from "vue-meta";
import root from "@/app";
import { stringify } from "querystring";
import { urlJoin, createRedirect, promisify } from "./utils";
const isDev = process.env.NODE_ENV !== "production";
const createNext = ssrContext => opts => {
ssrContext._status.redirected = true;
opts.query = stringify(opts.query);
opts.path = opts.path + (opts.query ? "?" + opts.query : "");
if (
opts.path.indexOf("http") !== 0 &&
("/" !== "/" && opts.path.indexOf("/") !== 0)
) {
opts.path = urlJoin("/", opts.path);
}
// Avoid loop redirect
if (opts.path === ssrContext.req.url) {
ssrContext._status.redirected = false;
return;
}
ssrContext.res.redirect(opts.status, opts.path);
};
// 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
});
export default context => {
return new Promise((resolve, reject) => {
if (!root.data || typeof root.data != "function") {
return reject(
new Error(
"The app.js file must have a data method, and the data must be a function"
)
);
}
const { app, router, store } = root.data(context);
context._status = {
redirected: false,
error: null,
serverRendered: true
};
context.next = createNext(context);
context.redirect = createRedirect(context, router, true);
context.isDev = isDev;
context.meta = app.$meta();
const { req } = context;
const { fullPath, params, query } = router.resolve(req.url).route;
context.params = params;
context.query = query;
if (fullPath !== req.url) {
return reject({ url: fullPath });
}
router.push(req.url);
// wait until router has resolved possible async hooks
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
if (req.url == "/404" || req.url.indexOf(".") >= 0) {
return reject({ code: 404 });
} else {
return reject({ url: "/404" });
}
}
let isValid = true;
matchedComponents.forEach(component => {
if (!isValid) return;
if (typeof component.validate !== "function") return;
isValid = component.validate({
params: params || {},
query: query || {}
});
});
if (!isValid) {
if (req.url == "/404" || req.url.indexOf(".") >= 0) {
return reject({ code: 404 });
} else {
return reject({ url: "/404" });
}
}
const asyncDataHooks = matchedComponents
.map(c => c.asyncData)
.filter(_ => _);
Promise.all(
asyncDataHooks.map(asyncData => {
if (typeof asyncData === "function") {
return promisify(asyncData, {
store,
route: router.currentRoute,
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 (context._status.redirected) return resolve(false);
context.state = store.state;
store.state.SSR_FETCHED = true;
return resolve(app);
})
.catch(reject);
}, reject);
});
};