update packages and add valign
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { LICENSE_BANNER } from './banner.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const root = resolve(__dirname, '..');
|
||||
|
||||
const targets = process.argv.slice(2);
|
||||
|
||||
for (const target of targets) {
|
||||
const absolutePath = resolve(root, target);
|
||||
const contents = await readFile(absolutePath, 'utf8');
|
||||
const withoutBanner = contents.startsWith(LICENSE_BANNER)
|
||||
? contents.slice(LICENSE_BANNER.length).trimStart()
|
||||
: contents.trimStart();
|
||||
|
||||
await writeFile(absolutePath, `${LICENSE_BANNER}\n${withoutBanner}`, 'utf8');
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export const LICENSE_BANNER = `/*!
|
||||
* reveal.js
|
||||
* https://revealjs.com
|
||||
* MIT licensed
|
||||
*
|
||||
* Copyright (C) 2011-2026 Hakim El Hattab, https://hakim.se
|
||||
*/`;
|
||||
@@ -0,0 +1,55 @@
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { transform } from 'esbuild';
|
||||
import ts from 'typescript';
|
||||
import { LICENSE_BANNER } from './banner.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const root = resolve(__dirname, '..');
|
||||
|
||||
const inputPath = resolve(root, 'dist/reveal.js');
|
||||
const outputPath = resolve(root, 'dist/reveal.es5.js');
|
||||
|
||||
try {
|
||||
const source = await readFile(inputPath, 'utf8');
|
||||
const result = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
module: ts.ModuleKind.UMD,
|
||||
downlevelIteration: true,
|
||||
removeComments: false,
|
||||
},
|
||||
fileName: 'reveal.js',
|
||||
reportDiagnostics: true,
|
||||
});
|
||||
|
||||
if (result.diagnostics?.length) {
|
||||
const errors = result.diagnostics
|
||||
.filter(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error)
|
||||
.map(diagnostic => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'));
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(`TypeScript transpilation failed:\n${errors.join('\n')}`);
|
||||
}
|
||||
}
|
||||
|
||||
const minified = await transform(result.outputText, {
|
||||
loader: 'js',
|
||||
target: ['es5'],
|
||||
minify: true,
|
||||
legalComments: 'none',
|
||||
});
|
||||
|
||||
await writeFile(outputPath, `${LICENSE_BANNER}\n${minified.code.trimStart()}`, 'utf8');
|
||||
|
||||
console.log(`Built ES5 UMD bundle: ${outputPath} (target: es5, minified)`);
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') {
|
||||
console.error(`Could not find ${inputPath}. Run "npm run build" first.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { glob } from 'glob';
|
||||
import { runQunitPuppeteer, printFailedTests } from 'node-qunit-puppeteer';
|
||||
import { createServer } from 'vite';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const root = resolve(__dirname, '..');
|
||||
|
||||
const testFiles = glob.sync('test/*.html', { cwd: root });
|
||||
const isCI = process.env.CI === 'true' || process.env.CI === '1';
|
||||
const puppeteerArgs = [
|
||||
'--allow-file-access-from-files',
|
||||
...( isCI ? [ '--no-sandbox', '--disable-setuid-sandbox' ] : [] )
|
||||
];
|
||||
|
||||
const combinedResults = { passed: 0, failed: 0, total: 0, runtime: 0, errors: 0 };
|
||||
|
||||
// Create and start Vite server (root = project root)
|
||||
const startServer = async () => {
|
||||
const server = await createServer({
|
||||
root,
|
||||
server: {
|
||||
port: 8009
|
||||
},
|
||||
});
|
||||
await server.listen();
|
||||
|
||||
const baseUrl = server.resolvedUrls?.local?.[0] || 'http://127.0.0.1:8009/';
|
||||
return { server, baseUrl };
|
||||
};
|
||||
|
||||
// Run tests
|
||||
const runTests = async (baseUrl) => {
|
||||
await Promise.all(
|
||||
testFiles.map(async (file) => {
|
||||
const qunitArgs = {
|
||||
targetUrl: new URL(file, baseUrl).href,
|
||||
timeout: 30000,
|
||||
redirectConsole: false,
|
||||
puppeteerArgs,
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await runQunitPuppeteer(qunitArgs);
|
||||
combinedResults.passed += result.stats.passed;
|
||||
combinedResults.failed += result.stats.failed;
|
||||
combinedResults.total += result.stats.total;
|
||||
combinedResults.runtime += result.stats.runtime;
|
||||
|
||||
if (result.stats.failed > 0) {
|
||||
console.log(
|
||||
`${'!'} ${file} [${result.stats.passed}/${result.stats.total}] in ${
|
||||
result.stats.runtime
|
||||
}ms`.red
|
||||
);
|
||||
printFailedTests(result, console);
|
||||
} else {
|
||||
console.log(
|
||||
`${'✔'} ${file} [${result.stats.passed}/${result.stats.total}] in ${
|
||||
result.stats.runtime
|
||||
}ms`.green
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
combinedResults.errors += 1;
|
||||
console.error(`Error running tests for ${file}:`, error);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
console.log(
|
||||
`\n${combinedResults.passed}/${combinedResults.total} tests passed, ${combinedResults.failed} failed, ${combinedResults.errors} errors, ${combinedResults.runtime}ms runtime`
|
||||
);
|
||||
|
||||
return combinedResults.failed > 0 || combinedResults.errors > 0 ? 1 : 0;
|
||||
};
|
||||
|
||||
// Main execution
|
||||
(async () => {
|
||||
let server;
|
||||
|
||||
try {
|
||||
const startedServer = await startServer();
|
||||
server = startedServer.server;
|
||||
|
||||
process.exitCode = await runTests(startedServer.baseUrl);
|
||||
} catch (error) {
|
||||
console.error('An error occurred:', error);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
if (server) {
|
||||
await server.close();
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,147 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user