148 lines
4.7 KiB
JavaScript
148 lines
4.7 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import JSZip from 'jszip';
|
|
import { globSync } from 'glob';
|
|
|
|
function switchToStaticScripts(htmlContent) {
|
|
// Look for the module script block and capture indentation
|
|
const moduleScriptPattern = /(\s*)<script type="module">([\s\S]*?)<\/script>/;
|
|
const match = htmlContent.match(moduleScriptPattern);
|
|
|
|
if (!match) return htmlContent;
|
|
|
|
const indentation = match[1].replace(/\n/g, '');
|
|
let moduleCode = match[2];
|
|
const scriptPaths = [];
|
|
const pluginAliasMap = new Map();
|
|
const moduleAliasMap = new Map();
|
|
|
|
const addScriptPath = (scriptPath) => {
|
|
if (!scriptPaths.includes(scriptPath)) {
|
|
scriptPaths.push(scriptPath);
|
|
}
|
|
};
|
|
|
|
const replaceIdentifier = (code, identifier, replacement) => {
|
|
if (!identifier || identifier === replacement) return code;
|
|
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
return code.replace(new RegExp(`\\b${escaped}\\b`, 'g'), replacement);
|
|
};
|
|
|
|
// Replace main reveal.js import.
|
|
moduleCode = moduleCode.replace(
|
|
/^\s*import\s+(\w+)\s+from\s+['"]reveal\.js['"]\s*;?\s*$/gm,
|
|
(match, revealVar) => {
|
|
addScriptPath('dist/reveal.js');
|
|
moduleAliasMap.set(revealVar, 'Reveal');
|
|
return '';
|
|
}
|
|
);
|
|
|
|
// Replace plugin imports
|
|
moduleCode = moduleCode.replace(
|
|
/^\s*import\s+(\w+)\s+from\s+['"]reveal\.js\/plugin\/(\w+)['"]\s*;?\s*$/gm,
|
|
(match, pluginVar, pluginName) => {
|
|
const pluginGlobal = `Reveal${pluginName.charAt(0).toUpperCase()}${pluginName.slice(1)}`;
|
|
addScriptPath(`dist/plugin/${pluginName}.js`);
|
|
pluginAliasMap.set(pluginVar, pluginGlobal);
|
|
return '';
|
|
}
|
|
);
|
|
|
|
for (const [pluginVar, pluginGlobal] of pluginAliasMap) {
|
|
moduleCode = replaceIdentifier(moduleCode, pluginVar, pluginGlobal);
|
|
}
|
|
for (const [moduleVar, moduleGlobal] of moduleAliasMap) {
|
|
moduleCode = replaceIdentifier(moduleCode, moduleVar, moduleGlobal);
|
|
}
|
|
|
|
// Clean up any remaining empty lines and trim
|
|
moduleCode = moduleCode.replace(/^\s*[\r\n]/gm, '').trim();
|
|
|
|
const scriptTags = scriptPaths.map((scriptPath) => `${indentation}<script src="${scriptPath}"></script>`);
|
|
const replacement =
|
|
'\n\n' +
|
|
scriptTags.join('\n') +
|
|
`\n${indentation}<script>\n${indentation}\t${moduleCode}\n${indentation}</script>`;
|
|
|
|
return htmlContent.replace(moduleScriptPattern, replacement);
|
|
}
|
|
|
|
/**
|
|
* Replace paths to dynamic CSS/SCSS files with static ones.
|
|
*/
|
|
function switchToStaticStyles(htmlContent) {
|
|
// Replace /css/* links with /dist/*
|
|
htmlContent = htmlContent.replace(/href="css\/([^"]+\.(css|scss))"/g, (match, filePath) => {
|
|
const cssPath = filePath.replace(/\.scss$/, '.css');
|
|
return `href="dist/${cssPath}"`;
|
|
});
|
|
|
|
// Replace /plugin/* links with /dist/plugin/*
|
|
htmlContent = htmlContent.replace(/href="plugin\/([^"]+\.(css|scss))"/g, (match, filePath) => {
|
|
const cssPath = filePath.replace(/\.scss$/, '.css');
|
|
return `href="dist/plugin/${cssPath}"`;
|
|
});
|
|
|
|
return htmlContent;
|
|
}
|
|
|
|
async function main() {
|
|
// Parse command line arguments for HTML file target
|
|
const args = process.argv.slice(2);
|
|
const htmlTarget = args.length > 0 ? args[0] : 'index.html';
|
|
|
|
// Ensure relative paths are read from cwd while keeping absolute paths intact
|
|
const targetFile = path.isAbsolute(htmlTarget)
|
|
? htmlTarget
|
|
: htmlTarget.startsWith('./')
|
|
? htmlTarget
|
|
: `./${htmlTarget}`;
|
|
|
|
console.log(`Packaging presentation with target file: ${targetFile}`);
|
|
|
|
// Read the HTML file
|
|
let htmlContent = fs.readFileSync(targetFile, 'utf8');
|
|
|
|
// Switch from Vite's dynamic imports to static ones so that
|
|
// this presentation can run anywhere (including offline via
|
|
// file:// protocol)
|
|
htmlContent = switchToStaticScripts(htmlContent);
|
|
htmlContent = switchToStaticStyles(htmlContent);
|
|
|
|
const zip = new JSZip();
|
|
const filesToInclude = ['./dist/**', './*/*.md'];
|
|
|
|
if (fs.existsSync('./lib')) filesToInclude.push('./lib/**');
|
|
if (fs.existsSync('./images')) filesToInclude.push('./images/**');
|
|
if (fs.existsSync('./slides')) filesToInclude.push('./slides/**');
|
|
|
|
// Add the modified HTML file first
|
|
const htmlFileName = htmlTarget.replace(/\.\//, '');
|
|
zip.file(htmlFileName, htmlContent);
|
|
|
|
for (const pattern of filesToInclude) {
|
|
const files = globSync(pattern, {
|
|
nodir: true,
|
|
dot: false,
|
|
ignore: ['./examples/**', './test/**'],
|
|
});
|
|
for (const file of files) {
|
|
const filePath = path.resolve(file);
|
|
const relativePath = path.relative(process.cwd(), filePath);
|
|
const fileData = fs.readFileSync(filePath);
|
|
zip.file(relativePath, fileData);
|
|
}
|
|
}
|
|
|
|
const content = await zip.generateAsync({ type: 'nodebuffer' });
|
|
const zipFileName = `presentation.zip`;
|
|
fs.writeFileSync(zipFileName, content);
|
|
console.log(`Presentation packaged successfully: ${zipFileName}`);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('Error packaging presentation:', error);
|
|
process.exit(1);
|
|
});
|