vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
156 lines (155 loc) • 6.22 kB
JavaScript
import '../../../assertEnvServer.js';
export { log404 };
// Exported for ./index.spec.ts
export { getRoutesInfo };
import { noRouteMatch } from '../../../../shared-server-client/route/noRouteMatch.js';
import { assert, assertUsage, assertInfo } from '../../../../utils/assert.js';
import { compareString } from '../../../../utils/compareString.js';
import { stripAnsi } from '../../../../utils/colorsServer.js';
import { getTerminalWidth } from '../../../../utils/getTerminalWidth.js';
import { truncateString } from '../../../../utils/truncateString.js';
import pc from '@brillout/picocolors';
function log404(pageContext) {
const { urlPathname } = pageContext;
const pageRoutes = pageContext._globalContext._pageRoutes;
assertUsage(pageRoutes.length > 0, 'No page found.');
const globalContext = pageContext._globalContext;
if (!globalContext._isProduction && !isFileRequest(urlPathname) && !pageContext.isClientSideNavigation) {
const routesInfo = getRoutesInfo(pageRoutes);
let msg = `URL ${pc.cyan(urlPathname)} ${noRouteMatch}`;
const outro = 'See https://vike.dev/routing for more information about routing.';
if (!routesInfo) {
msg = `${msg}. ${pc.dim(outro)}`;
}
else {
msg = `${msg}:\n${routesInfo}\n${outro}`;
}
assertInfo(false, msg, { onlyOnce: false });
}
}
function getRoutesInfo(pageRoutes) {
const entries = pageRoutes
.map((pageRoute) => {
let routeStr;
let routeTypeSrc;
let routeDefinedBy;
if (pageRoute.routeType === 'FILESYSTEM') {
assert(pageRoute.routeFilesystemDefinedBy);
routeDefinedBy = pageRoute.routeFilesystemDefinedBy;
}
else {
assert(pageRoute.routeDefinedAtString);
routeDefinedBy = pageRoute.routeDefinedAtString;
}
if (pageRoute.routeType === 'STRING') {
routeStr = pageRoute.routeString;
routeTypeSrc = 'Route String';
}
else if (pageRoute.routeType === 'FUNCTION') {
routeStr = String(pageRoute.routeFunction);
routeTypeSrc = 'Route Function';
}
else {
routeStr = pageRoute.routeString;
routeTypeSrc = 'Filesystem Route';
}
assert(routeStr && routeTypeSrc && routeDefinedBy);
return { routeStr, routeTypeSrc, routeDefinedBy };
})
.sort((e1, e2) => {
if (e1.routeTypeSrc !== 'Route Function' && e2.routeTypeSrc === 'Route Function') {
return -1;
}
if (e1.routeTypeSrc === 'Route Function' && e2.routeTypeSrc !== 'Route Function') {
return 1;
}
return compareString(e1.routeStr, e2.routeStr);
});
const linesContent = [
{
routeStr: 'ROUTE',
routeTypeSrc: 'TYPE',
routeDefinedBy: 'DEFINED BY',
},
...entries,
];
const terminalWidth = getTerminalWidth() || 134;
let width2 = Math.max(...linesContent.map(({ routeTypeSrc }) => stripAnsi(routeTypeSrc).length));
let width3 = Math.max(...linesContent.map(({ routeDefinedBy }) => stripAnsi(routeDefinedBy).length));
const width1_max = terminalWidth -
width3 -
width2 -
// Total width of table border & padding
10;
if (width1_max < 10)
return null;
linesContent.forEach((lineContent) => {
let { routeStr } = lineContent;
if (lineContent.routeTypeSrc === 'Route Function') {
routeStr = truncateRouteFunction(routeStr, width1_max);
assert(stripAnsi(routeStr).length <= width1_max);
}
lineContent.routeStr = routeStr;
});
let width1 = Math.max(...linesContent.map(({ routeStr }) => stripAnsi(routeStr).length));
if (width1 > width1_max)
return null;
let lines = linesContent.map(({ routeStr, routeTypeSrc, routeDefinedBy }, i) => {
let cell1 = padEnd(routeStr, width1 + (stripAnsi(routeStr).length - stripAnsi(routeStr).length));
let cell2 = padEnd(routeTypeSrc, width2);
let cell3 = padEnd(routeDefinedBy, width3);
const isHeader = i === 0;
if (isHeader) {
cell1 = pc.dim(cell1);
cell2 = pc.dim(cell2);
cell3 = pc.dim(cell3);
}
let line = [cell1, cell2, cell3].join(pc.dim(' │ '));
line = pc.dim('│ ') + line + pc.dim(' │');
return line;
});
width1 = width1 + 2;
width2 = width2 + 2;
width3 = width3 + 2;
// https://en.wikipedia.org/wiki/Box-drawing_character
lines = [
pc.dim(`┌${'─'.repeat(width1)}┬${'─'.repeat(width2)}┬${'─'.repeat(width3)}┐`),
lines[0],
pc.dim(`├${'─'.repeat(width1)}┼${'─'.repeat(width2)}┼${'─'.repeat(width3)}┤`),
...lines.slice(1),
pc.dim(`└${'─'.repeat(width1)}┴${'─'.repeat(width2)}┴${'─'.repeat(width3)}┘`),
];
lines.forEach((line) => {
assert(stripAnsi(line).length <= terminalWidth);
});
return lines.join('\n');
}
function truncateRouteFunction(routeStr, lenMax) {
routeStr = stripAnsi(routeStr);
routeStr = removeNonAscii(routeStr);
routeStr = routeStr.split(/\s/).filter(Boolean).join(' ');
routeStr = truncateString(routeStr, lenMax);
return routeStr;
}
/** Same as String.prototype.padEnd but with stripAnsi() */
function padEnd(str, width) {
const padWidth = Math.max(0, width - stripAnsi(str).length);
return str + ''.padEnd(padWidth, ' ');
}
function removeNonAscii(str) {
// https://stackoverflow.com/questions/20856197/remove-non-ascii-character-in-string/20856346#20856346
return str.replace(/[^\x00-\x7F]/g, '');
}
function isFileRequest(urlPathname) {
assert(urlPathname.startsWith('/'));
const paths = urlPathname.split('/');
const lastPath = paths[paths.length - 1];
assert(typeof lastPath === 'string');
const parts = lastPath.split('.');
if (parts.length < 2) {
return false;
}
const fileExtension = parts[parts.length - 1];
assert(typeof fileExtension === 'string');
return /^[a-z0-9]+$/.test(fileExtension);
}