@itznotabug/routex
Version:
A client side redirection plugin for Vitepress.
84 lines (81 loc) • 3.64 kB
JavaScript
import path from 'path';
import { promises as fs } from 'fs';
import { Logger } from './logger.js';
import { Template } from './template.js';
import { sanitizePath } from './utils.js';
/**
* Handles generation of redirect files and scripts
*/
export class RedirectGenerator {
rules;
options;
vitepressConfig;
constructor(rules, options, vitepressConfig) {
this.rules = rules;
this.options = options;
this.vitepressConfig = vitepressConfig;
}
injectDevelopmentClientScript(html) {
const redirectsJson = JSON.stringify(this.rules, null, 2);
const scriptContent = `
function performRedirect() {
const redirects = ${redirectsJson};
const destination = redirects[location.pathname];
if (destination) {
try {
location.replace(destination);
} catch (error) {
console.warn('Redirect failed, using fallback:', error);
location.href = destination;
}
}
}
performRedirect();
`.trim();
const script = `<script>\n ${scriptContent.split('\n').join('\n ')}\n </script>`;
return html.replace('<head>', `<head>\n ${script}`);
}
injectProductionClientScript(html, destination) {
// quick, small and efficient
const scriptContent = `var d=${JSON.stringify(destination)};try{location.replace(d)}catch(e){location.href=d}`;
const script = `<script>${scriptContent}</script>`;
return html.replace('<head>', `<head>\n ${script}`);
}
async generateRedirectPage(source, destination, templateManager) {
const delay = this.options.redirectDelay || 0;
const template = await templateManager.resolveTemplate();
const metaTags = this.createMetaTags(destination, delay);
const processedTemplate = templateManager.processTemplate(template, source, destination, metaTags, delay);
if (delay === 0) {
// faster redirect if delay is 0.
return this.injectProductionClientScript(processedTemplate, destination);
}
return processedTemplate;
}
async generateAllRedirectFiles(outDir) {
const redirectCount = Object.keys(this.rules).length;
if (redirectCount === 0)
return;
Logger.log(`Generating ${redirectCount} redirect page${redirectCount > 1 ? 's' : ''}...`, true);
const templateManager = new Template(this.options, this.vitepressConfig);
await Promise.all(Object.entries(this.rules).map(async ([from, to]) => {
const filePath = path.join(outDir, sanitizePath(from), 'index.html');
const html = await this.generateRedirectPage(from, to, templateManager);
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, html, 'utf8');
}));
Logger.log(`✓ Generated ${redirectCount} redirect page${redirectCount > 1 ? 's' : ''}`);
}
createMetaTags(destination, delay) {
const tags = [];
// Meta refresh for SEO, crawlers, and non-JS fallback
tags.push(`<meta http-equiv="refresh" content="${delay}; url=${destination}">`);
if (this.options.addNoIndexMeta) {
tags.push(`<meta name="robots" content="noindex, nofollow">`);
}
if (this.options.addCanonical) {
tags.push(`<link rel="canonical" href="${destination}">`);
}
return tags.join('\n ');
}
}