const fs = require('fs');
const path = require('path');
const utils = require('./utils');
const config = require('./config');
const entryAbsPath = path.resolve(__dirname, config.entry);
const entryDirname = path.dirname(entryAbsPath);
const router = (ctx) => {
const { url = '' } = ctx;
if (url === '/') {
handleRootRouter(ctx);
} else if (utils.getFileExtname(url) === '.js') {
handleJsRouter(ctx);
} else if (url.startsWith('/node_modules')) {
handleLibRouter(ctx);
} else if (utils.getFileExtname(url) === '.vue') {
handleVueRouter(ctx);
}
};
const handleRootRouter = (ctx) => {
const entryCodeStr = fs.readFileSync(entryAbsPath, 'utf-8');
ctx.type = 'text/html; charset=utf-8';
utils.setStrongCache(ctx);
ctx.body = entryCodeStr;
};
const handleJsRouter = ctx => {
const fileAbsPath = path.resolve(entryDirname, `.${ctx.url}`);
const jsCodeStr = fs.readFileSync(fileAbsPath, 'utf-8');
const transformCodeStr = utils.transformPath(jsCodeStr);
ctx.type = 'application/javascript';
ctx.body = transformCodeStr;
};
const handleLibRouter = (ctx) => {
const libAbsPath = path.resolve(entryDirname, `../${ctx.url}`);
const { module: libBundlePath } = require(`${libAbsPath}/package.json`);
const libBundleAbsPath = `${libAbsPath}/${libBundlePath}`;
const libBundleStr = fs.readFileSync(libBundleAbsPath, 'utf-8');
const transformCodeStr = utils.transformPath(libBundleStr);
utils.setStrongCache(ctx);
ctx.type = 'application/javascript';
ctx.body = transformCodeStr;
};
const handleVueRouter = (ctx) => {
const url = ctx.url.split('?')[0];
const fileAbsPath = path.resolve(entryDirname, `.${url}`);
const vueCodeStr = fs.readFileSync(fileAbsPath, 'utf-8');
const { jsCodeStr, transformRenderModuleStr: renderModuleStr } = utils.parseVue(url, vueCodeStr);
const { type } = ctx.query;
let body = '';
if (type === 'template') {
body = renderModuleStr;
} else {
body = jsCodeStr;
}
ctx.type = 'application/javascript';
ctx.body = body;
};
module.exports = router;
const path = require('path');
const parser = require('@babel/parser');
const { default: traverse } = require('@babel/traverse');
const generator = require("@babel/generator");
const compilerSFC = require('@vue/compiler-sfc');
const compilerDOM = require('@vue/compiler-dom');
const transformPath = (source = '') => {
const ast = parser.parse(source, {
sourceType: "module",
});
const rewritePath = (node) => {
const esmPath = node?.source?.value;
if (!isLegalEsmPath(esmPath) && esmPath) {
node.source.value = `/node_modules/${esmPath}`;
}
};
traverse(ast, {
ImportDeclaration({node}) {
rewritePath(node);
},
ExportAllDeclaration({node}) {
rewritePath(node);
},
ExportNamedDeclaration({node}) {
rewritePath(node);
}
});
const transformSource = generator.default(ast, {}, source).code;
return transformSource;
};
const isLegalEsmPath = esmPath => {
const legalEsmPath = ['/', './', '../'];
let isLegalEsmPath = false;
legalEsmPath.some(path => {
if (esmPath?.startsWith(path)) {
isLegalEsmPath = true;
return true;
}
});
return isLegalEsmPath;
};
const parseVue = (url, source) => {
const ast = compilerSFC.parse(source);
const { template, script } = ast.descriptor;
const scriptCode = script.content.replace(/export default/, 'const script =');
const transformScriptCode = transformPath(scriptCode);
const { code: renderModule } = compilerDOM.compile(template.content, {mode: 'module'});
const transformRenderModuleStr = transformPath(renderModule);
const jsCodeStr = `
${transformScriptCode}
import { render } from '${url}?type=template';
script.render = render;
export default script;
`;
return {
transformRenderModuleStr,
jsCodeStr
};
};
const getFileExtname = (urlPath = '') => {
const filePath = urlPath.split('?')[0];
const extname = path.extname(filePath);
return extname;
};
const setStrongCache = (ctx) => {
ctx.set('Cache-Control', 'max-age=31536000,immutable');
};
const setConsultCache = () => {
ctx['no-cache'] = true;
};
module.exports = {
transformPath,
parseVue,
getFileExtname,
setStrongCache,
setConsultCache,
};
参考
github.com/classmatewu…