249 lines
No EOL
8.4 KiB
JavaScript
249 lines
No EOL
8.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Build custom highlight.js bundle by downloading and combining CDN files
|
|
* This creates a single local file with all required languages
|
|
*
|
|
* Usage:
|
|
* node scripts/build-highlight-bundle.js
|
|
*
|
|
* This generates a bundle at public/js/highlight-bundle.min.js
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import https from 'https';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
/**
|
|
* Discover available languages by scanning content/locales directory
|
|
* This mimics the Rust discover_available_languages() function
|
|
*/
|
|
async function discoverAvailableLanguages() {
|
|
const localesPath = process.env.SITE_I18N_PATH ? path.join(process.env.SITE_I18N_PATH, 'locales') : path.join('site', 'i18n', 'locales');
|
|
|
|
try {
|
|
if (!fs.existsSync(localesPath)) {
|
|
console.log(`⚠️ Locales directory not found: ${localesPath}, using fallback languages`);
|
|
return ['en', 'es'];
|
|
}
|
|
|
|
const entries = fs.readdirSync(localesPath, { withFileTypes: true });
|
|
const languages = entries
|
|
.filter(entry => entry.isDirectory())
|
|
.map(entry => entry.name)
|
|
.filter(name => name.length === 2) // Only 2-letter language codes
|
|
.sort();
|
|
|
|
if (languages.length === 0) {
|
|
console.log(`⚠️ No language directories found in ${localesPath}, using fallback`);
|
|
return ['en', 'es'];
|
|
}
|
|
|
|
console.log(`🌐 Discovered languages: ${languages.join(', ')}`);
|
|
return languages;
|
|
} catch (error) {
|
|
console.log(`⚠️ Error discovering languages: ${error.message}, using fallback`);
|
|
return ['en', 'es'];
|
|
}
|
|
}
|
|
|
|
// Languages we want to include (in addition to core languages)
|
|
const additionalLanguages = [
|
|
'rust',
|
|
'typescript',
|
|
'bash',
|
|
'yaml',
|
|
'dockerfile',
|
|
'sql',
|
|
'python',
|
|
'ini', // For TOML-like syntax
|
|
'properties', // Also TOML-like syntax
|
|
'markdown',
|
|
'nix' // Closest grammar for Nickel (NCL) blocks tagged ```nix
|
|
];
|
|
|
|
const CDN_BASE = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0';
|
|
const HIGHLIGHTJS_COPY_VERSION = '1.0.6';
|
|
const COPY_PLUGIN_BASE = `https://unpkg.com/highlightjs-copy@${HIGHLIGHTJS_COPY_VERSION}/dist`;
|
|
|
|
function downloadFile(url) {
|
|
return new Promise((resolve, reject) => {
|
|
https.get(url, (response) => {
|
|
if (response.statusCode !== 200) {
|
|
reject(new Error(`HTTP ${response.statusCode}: ${url}`));
|
|
return;
|
|
}
|
|
|
|
let data = '';
|
|
response.on('data', (chunk) => data += chunk);
|
|
response.on('end', () => resolve(data));
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
async function buildBundle() {
|
|
try {
|
|
// Discover available languages first
|
|
const availableLanguages = await discoverAvailableLanguages();
|
|
|
|
// Ensure output directory exists first
|
|
const outputDir = path.join(__dirname, '../../site/public/js');
|
|
if (!fs.existsSync(outputDir)) {
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
}
|
|
|
|
// Check if bundle already exists and is recent (less than 24 hours old)
|
|
const outputPath = path.join(outputDir, 'highlight-bundle.min.js');
|
|
|
|
if (fs.existsSync(outputPath)) {
|
|
const stats = fs.statSync(outputPath);
|
|
const fileAge = Date.now() - stats.mtime.getTime();
|
|
const maxAge = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
|
|
if (fileAge < maxAge) {
|
|
const fileSizeKB = Math.round(stats.size / 1024);
|
|
const ageHours = Math.round(fileAge / (60 * 60 * 1000));
|
|
|
|
console.log('✅ Highlight.js bundle already exists and is recent!');
|
|
console.log(`📁 File: ${outputPath}`);
|
|
console.log(`📊 Size: ${fileSizeKB}KB`);
|
|
console.log(`⏰ Age: ${ageHours}h (created: ${stats.mtime.toLocaleString()})`);
|
|
console.log('🎯 Languages: Core JS/HTML/CSS/JSON/XML + ' + additionalLanguages.join(', '));
|
|
console.log('💡 To force rebuild, delete the file or wait 24 hours');
|
|
return;
|
|
} else {
|
|
console.log('🔄 Bundle exists but is older than 24 hours, rebuilding...');
|
|
}
|
|
}
|
|
|
|
console.log('🔨 Building highlight.js bundle from CDN...');
|
|
console.log(`📦 Including core + ${additionalLanguages.length} additional languages: ${additionalLanguages.join(', ')}`);
|
|
|
|
// Download core highlight.js
|
|
console.log('📥 Downloading core highlight.js...');
|
|
const coreJs = await downloadFile(`${CDN_BASE}/highlight.min.js`);
|
|
|
|
// Download highlightjs-copy plugin
|
|
console.log('📥 Downloading highlightjs-copy plugin...');
|
|
const copyPluginJs = await downloadFile(`${COPY_PLUGIN_BASE}/highlightjs-copy.min.js`);
|
|
|
|
let bundleContent = `/*! Custom Highlight.js Bundle for Rustelo
|
|
* Generated on ${new Date().toISOString()}
|
|
* Core + Additional Languages: ${additionalLanguages.join(', ')}
|
|
* Based on Highlight.js 11.9.0 from CDN
|
|
* Includes highlightjs-copy plugin v${HIGHLIGHTJS_COPY_VERSION}
|
|
*/
|
|
|
|
// Core highlight.js
|
|
${coreJs}
|
|
|
|
// Copy button plugin
|
|
${copyPluginJs}
|
|
|
|
// Additional language definitions
|
|
(function() {
|
|
if (typeof hljs === 'undefined') {
|
|
console.error('Highlight.js core not available');
|
|
return;
|
|
}
|
|
|
|
`;
|
|
|
|
// Download and add each language
|
|
console.log('📝 Downloading language definitions...');
|
|
|
|
for (const lang of additionalLanguages) {
|
|
try {
|
|
console.log(` 📥 Downloading: ${lang}`);
|
|
const langJs = await downloadFile(`${CDN_BASE}/languages/${lang}.min.js`);
|
|
|
|
// Wrap the language code to register it properly
|
|
bundleContent += `
|
|
// Language: ${lang}
|
|
(function() {
|
|
${langJs}
|
|
})();
|
|
`;
|
|
console.log(` ✅ Added: ${lang}`);
|
|
} catch (error) {
|
|
console.log(` ❌ Failed to download ${lang}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Close the bundle
|
|
bundleContent += `
|
|
})();
|
|
|
|
// Expose available languages globally for use by other scripts
|
|
if (typeof window !== 'undefined') {
|
|
window.__AVAILABLE_LANGUAGES = ${JSON.stringify(availableLanguages)};
|
|
} else if (typeof globalThis !== 'undefined') {
|
|
globalThis.__AVAILABLE_LANGUAGES = ${JSON.stringify(availableLanguages)};
|
|
}
|
|
|
|
// Auto-initialize when DOM is ready
|
|
if (typeof document !== 'undefined') {
|
|
function initializeHighlightJs() {
|
|
if (typeof hljs !== 'undefined' && hljs.highlightAll) {
|
|
hljs.configure({ ignoreUnescapedHTML: true });
|
|
hljs.highlightAll();
|
|
|
|
// Add copy button plugin with dynamic language detection and autohide configuration
|
|
if (typeof CopyButtonPlugin !== 'undefined') {
|
|
const docLang = document.documentElement.lang || 'en';
|
|
|
|
// Dynamically discovered available languages from content/locales
|
|
const AVAILABLE_LANGUAGES = ${JSON.stringify(availableLanguages)};
|
|
|
|
// Build language configuration for all available languages
|
|
const langConfig = {};
|
|
AVAILABLE_LANGUAGES.forEach(lang => {
|
|
langConfig[lang] = { autohide: false, lang: lang };
|
|
});
|
|
|
|
// Use detected language or fallback to first available language
|
|
const config = langConfig[docLang] || langConfig[AVAILABLE_LANGUAGES[0]] || { autohide: false, lang: 'en' };
|
|
hljs.addPlugin(new CopyButtonPlugin(config));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeHighlightJs);
|
|
} else {
|
|
// DOM already ready
|
|
initializeHighlightJs();
|
|
}
|
|
}
|
|
`;
|
|
|
|
// Write the bundle (directory already created at the start)
|
|
fs.writeFileSync(outputPath, bundleContent);
|
|
|
|
// Get file size
|
|
const stats = fs.statSync(outputPath);
|
|
const fileSizeKB = Math.round(stats.size / 1024);
|
|
|
|
console.log(`✅ Highlight.js bundle created successfully!`);
|
|
console.log(`📁 Output: ${outputPath}`);
|
|
console.log(`📊 Size: ${fileSizeKB}KB`);
|
|
console.log(`🎯 Languages: Core JS/HTML/CSS/JSON/XML + ${additionalLanguages.join(', ')}`);
|
|
console.log(`📋 Plugin: highlightjs-copy v${HIGHLIGHTJS_COPY_VERSION} (with i18n support)`);
|
|
console.log('');
|
|
console.log('🚀 Ready to use! The bundle includes:');
|
|
console.log(' - Auto-initialization on DOM ready');
|
|
console.log(' - All required languages pre-registered');
|
|
console.log(' - Copy code buttons with language detection (en/es)');
|
|
console.log(' - Single HTTP request instead of multiple CDN calls');
|
|
|
|
} catch (error) {
|
|
console.error('❌ Error building bundle:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
buildBundle(); |