flow-vue-ssr-middleware
Version:
基于flow-build,创建的支持vue服务端渲染(vue-ssr)的中间件
175 lines (158 loc) • 4.44 kB
JavaScript
const fs = require("fs");
const path = require("path");
const { createBundleRenderer } = require("vue-server-renderer");
const tryRequire = require("./try-require");
let renderer, options;
module.exports = async option => {
options = Object.assign(
{
output: "./dist",
error: (err, req, res) => {
if (err.code === 404) {
res && res.status(404).send("404 | Page Not Found");
} else {
res && res.status(500).send("500 | Internal Server Error");
console.error(`error during render : ${req.url}`);
console.error(err.stack || err);
}
}
},
option
);
if (process.env.NODE_ENV != "production") {
const SSRBuilder = tryRequire("flow-build");
if (!SSRBuilder) {
console.log("Please npm install --save-dev flow-build");
throw new Error(
"[flow-vue-ssr-middleware] SSRBuilder: true requires flow-build " +
"as a peer dependency."
);
return false;
}
try {
let flowConfig = require(path.resolve(
process.cwd(),
"./flow.config.js"
));
let builder = new SSRBuilder(flowConfig);
let { devMiddleware, hotMiddleware } = await builder.build(
createRenderer
);
return {
devMiddleware,
hotMiddleware,
middleware,
openBrowser: builder.openBrowser
};
} catch (error) {
console.error(error);
process.exit(1);
}
} else {
createRenderer();
return {
middleware
};
}
};
/**
* 中间件
* @param {*} ctx
*/
async function middleware(...ctx) {
let req, res, next;
if (ctx[0].hasOwnProperty("req") && ctx[0].hasOwnProperty("res")) {
req = ctx[0].req;
res = ctx[0].res;
next = ctx[1];
} else {
[req, res, next] = ctx;
}
if (req.method !== "GET" && req.method !== "HEAD") {
res.statusCode = 405;
res.setHeader("Allow", "GET, HEAD");
res.setHeader("Content-Length", "0");
res.end();
return;
}
if (req.path.indexOf(".") > 0) {
res.redirect("/404?from=" + decodeURIComponent(req.url));
return;
}
let result = await render(req, res);
if (isBoolean(result) && result) {
next();
} else if (!result.url) {
if (res.headersSent) {
options.error(result, req);
} else {
options.error(result, req, res);
}
}
}
/**
* 创建renderer实例
* @param {*} mfs
*/
function createRenderer(mfs = fs) {
const template = fs.readFileSync(options.template, "utf-8");
let distPath = path.resolve(process.cwd(), options.output);
const bundle = JSON.parse(
mfs.readFileSync(
path.resolve(distPath, "./server-bundle.json"),
"utf-8"
)
);
const clientManifest = JSON.parse(
mfs.readFileSync(
path.resolve(distPath, "./vue-ssr-client-manifest.json"),
"utf-8"
)
);
let option = {
cache: false,
basedir: distPath,
runInNewContext: false,
template,
clientManifest
};
if (process.env.NODE_ENV === "production" && options.cache) {
option.cache = options.cache;
}
renderer = createBundleRenderer(bundle, option);
}
/**
* 渲染页面
* @param {*} req
* @param {*} res
*/
function render(req, res) {
return new Promise(resolve => {
let context = Object.assign(
{
req: req,
res: res
},
options.context
);
renderer.renderToString(context, (err, html) => {
if (err) {
if (err.url) {
res.redirect(err.url);
}
return resolve(err);
} else if (!res.headersSent && html) {
res.setHeader("Content-Type", "text/html");
res.send(html);
}
return resolve(true);
});
});
}
/**
* 判断是否是bool值
* @param {*} v
*/
function isBoolean(v) {
return typeof v === "boolean";
}