返回 reveal.js
zip.js
根目录 / scripts / zip.js
1 import fs from 'node:fs';
2 import path from 'node:path';
3 import JSZip from 'jszip';
4 import { globSync } from 'glob';
5
6 function switchToStaticScripts(htmlContent) {
7 // Look for the module script block and capture indentation
8 const moduleScriptPattern = /(\s*)<script type="module">([\s\S]*?)<\/script>/;
9 const match = htmlContent.match(moduleScriptPattern);
10
11 if (!match) return htmlContent;
12
13 const indentation = match[1].replace(/\n/g, '');
14 let moduleCode = match[2];
15 const scriptPaths = [];
16 const pluginAliasMap = new Map();
17 const moduleAliasMap = new Map();
18
19 const addScriptPath = (scriptPath) => {
20 if (!scriptPaths.includes(scriptPath)) {
21 scriptPaths.push(scriptPath);
22 }
23 };
24
25 const replaceIdentifier = (code, identifier, replacement) => {
26 if (!identifier || identifier === replacement) return code;
27 const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
28 return code.replace(new RegExp(`\\b${escaped}\\b`, 'g'), replacement);
29 };
30
31 // Replace main reveal.js import.
32 moduleCode = moduleCode.replace(
33 /^\s*import\s+(\w+)\s+from\s+['"]reveal\.js['"]\s*;?\s*$/gm,
34 (match, revealVar) => {
35 addScriptPath('dist/reveal.js');
36 moduleAliasMap.set(revealVar, 'Reveal');
37 return '';
38 }
39 );
40
41 // Replace plugin imports
42 moduleCode = moduleCode.replace(
43 /^\s*import\s+(\w+)\s+from\s+['"]reveal\.js\/plugin\/(\w+)['"]\s*;?\s*$/gm,
44 (match, pluginVar, pluginName) => {
45 const pluginGlobal = `Reveal${pluginName.charAt(0).toUpperCase()}${pluginName.slice(1)}`;
46 addScriptPath(`dist/plugin/${pluginName}.js`);
47 pluginAliasMap.set(pluginVar, pluginGlobal);
48 return '';
49 }
50 );
51
52 for (const [pluginVar, pluginGlobal] of pluginAliasMap) {
53 moduleCode = replaceIdentifier(moduleCode, pluginVar, pluginGlobal);
54 }
55 for (const [moduleVar, moduleGlobal] of moduleAliasMap) {
56 moduleCode = replaceIdentifier(moduleCode, moduleVar, moduleGlobal);
57 }
58
59 // Clean up any remaining empty lines and trim
60 moduleCode = moduleCode.replace(/^\s*[\r\n]/gm, '').trim();
61
62 const scriptTags = scriptPaths.map((scriptPath) => `${indentation}<script src="${scriptPath}"></script>`);
63 const replacement =
64 '\n\n' +
65 scriptTags.join('\n') +
66 `\n${indentation}<script>\n${indentation}\t${moduleCode}\n${indentation}</script>`;
67
68 return htmlContent.replace(moduleScriptPattern, replacement);
69 }
70
71 /**
72 * Replace paths to dynamic CSS/SCSS files with static ones.
73 */
74 function switchToStaticStyles(htmlContent) {
75 // Replace /css/* links with /dist/*
76 htmlContent = htmlContent.replace(/href="css\/([^"]+\.(css|scss))"/g, (match, filePath) => {
77 const cssPath = filePath.replace(/\.scss$/, '.css');
78 return `href="dist/${cssPath}"`;
79 });
80
81 // Replace /plugin/* links with /dist/plugin/*
82 htmlContent = htmlContent.replace(/href="plugin\/([^"]+\.(css|scss))"/g, (match, filePath) => {
83 const cssPath = filePath.replace(/\.scss$/, '.css');
84 return `href="dist/plugin/${cssPath}"`;
85 });
86
87 return htmlContent;
88 }
89
90 async function main() {
91 // Parse command line arguments for HTML file target
92 const args = process.argv.slice(2);
93 const htmlTarget = args.length > 0 ? args[0] : 'index.html';
94
95 // Ensure relative paths are read from cwd while keeping absolute paths intact
96 const targetFile = path.isAbsolute(htmlTarget)
97 ? htmlTarget
98 : htmlTarget.startsWith('./')
99 ? htmlTarget
100 : `./${htmlTarget}`;
101
102 console.log(`Packaging presentation with target file: ${targetFile}`);
103
104 // Read the HTML file
105 let htmlContent = fs.readFileSync(targetFile, 'utf8');
106
107 // Switch from Vite's dynamic imports to static ones so that
108 // this presentation can run anywhere (including offline via
109 // file:// protocol)
110 htmlContent = switchToStaticScripts(htmlContent);
111 htmlContent = switchToStaticStyles(htmlContent);
112
113 const zip = new JSZip();
114 const filesToInclude = ['./dist/**', './*/*.md'];
115
116 if (fs.existsSync('./lib')) filesToInclude.push('./lib/**');
117 if (fs.existsSync('./images')) filesToInclude.push('./images/**');
118 if (fs.existsSync('./slides')) filesToInclude.push('./slides/**');
119
120 // Add the modified HTML file first
121 const htmlFileName = htmlTarget.replace(/\.\//, '');
122 zip.file(htmlFileName, htmlContent);
123
124 for (const pattern of filesToInclude) {
125 const files = globSync(pattern, {
126 nodir: true,
127 dot: false,
128 ignore: ['./examples/**', './test/**'],
129 });
130 for (const file of files) {
131 const filePath = path.resolve(file);
132 const relativePath = path.relative(process.cwd(), filePath);
133 const fileData = fs.readFileSync(filePath);
134 zip.file(relativePath, fileData);
135 }
136 }
137
138 const content = await zip.generateAsync({ type: 'nodebuffer' });
139 const zipFileName = `presentation.zip`;
140 fs.writeFileSync(zipFileName, content);
141 console.log(`Presentation packaged successfully: ${zipFileName}`);
142 }
143
144 main().catch((error) => {
145 console.error('Error packaging presentation:', error);
146 process.exit(1);
147 });
148
148 lines JAVASCRIPT