Skip to content

Commit afde8db

Browse files
committed
refactor(archive): use jszip to compress fs created folder
1 parent 9ac27b8 commit afde8db

File tree

7 files changed

+131
-54
lines changed

7 files changed

+131
-54
lines changed

src/automizer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,15 @@ export default class Automizer implements IPresentationProps {
8484
if (params.presTemplates) {
8585
this.params.presTemplates.forEach((file) => {
8686
const location = this.getLocation(file, 'template');
87+
const archiveParams = {
88+
...this.archiveParams,
89+
name: file,
90+
};
8791
const newTemplate = Template.import(
8892
location,
89-
this.archiveParams,
93+
archiveParams,
9094
) as PresTemplate;
95+
9196
this.templates.push(newTemplate);
9297
});
9398
}

src/dev.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@ const automizer = new Automizer({
1111
workDir: outputName,
1212
cleanupWorkDir: true,
1313
},
14+
rootTemplate: 'RootTemplateWithImages.pptx',
15+
presTemplates: [
16+
`RootTemplate.pptx`,
17+
`SlideWithImages.pptx`,
18+
`ChartBarsStacked.pptx`,
19+
],
1420
removeExistingSlides: true,
1521
cleanup: true,
16-
compression: 5,
22+
compression: 0,
1723
});
1824

1925
const run = async () => {
20-
const pres = automizer
21-
.loadRoot(`RootTemplateWithImages.pptx`)
22-
.load(`RootTemplate.pptx`, 'root')
23-
.load(`SlideWithImages.pptx`, 'images')
24-
.load(`ChartBarsStacked.pptx`, 'charts');
26+
// const pres = automizer
27+
// .loadRoot(`RootTemplateWithImages.pptx`)
28+
// .load(`RootTemplate.pptx`, 'root')
29+
// .load(`SlideWithImages.pptx`, 'images')
30+
// .load(`ChartBarsStacked.pptx`, 'charts');
2531

2632
const dataSmaller = {
2733
series: [{ label: 'series s1' }, { label: 'series s2' }],
@@ -31,27 +37,25 @@ const run = async () => {
3137
],
3238
};
3339

34-
const result = await pres
35-
.addSlide('charts', 1, (slide) => {
40+
const result = await automizer
41+
.addSlide('ChartBarsStacked.pptx', 1, (slide) => {
3642
slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]);
37-
slide.addElement('charts', 1, 'BarsStacked', [
43+
slide.addElement('ChartBarsStacked.pptx', 1, 'BarsStacked', [
3844
modify.setChartData(dataSmaller),
3945
]);
4046
})
41-
.addSlide('images', 1)
42-
.addSlide('root', 1, (slide) => {
43-
slide.addElement('charts', 1, 'BarsStacked', [
47+
.addSlide('SlideWithImages.pptx', 1)
48+
.addSlide('RootTemplate.pptx', 1, (slide) => {
49+
slide.addElement('ChartBarsStacked.pptx', 1, 'BarsStacked', [
4450
modify.setChartData(dataSmaller),
4551
]);
4652
})
47-
.addSlide('charts', 1, (slide) => {
48-
slide.addElement('images', 2, 'imageJPG');
53+
.addSlide('ChartBarsStacked.pptx', 1, (slide) => {
54+
slide.addElement('SlideWithImages.pptx', 2, 'imageJPG');
4955
slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]);
5056
})
51-
.addSlide('charts', 1, (slide) => {
52-
slide.addElement('images', 2, 'imageJPG');
53-
slide.addElement('images', 2, 'imageSVG');
54-
slide.addElement('images', 2, 'imageSVG');
57+
.addSlide('ChartBarsStacked.pptx', 1, (slide) => {
58+
slide.addElement('SlideWithImages.pptx', 2, 'imageJPG');
5559
slide.modifyElement('BarsStacked', [modify.setChartData(dataSmaller)]);
5660
})
5761
.write(outputName);

src/helper/archive/archive-fs.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Archive from './archive';
22
import fs, { promises as fsPromises } from 'fs';
33
import JSZip, { InputType } from 'jszip';
4-
import { ArchiveParams } from '../../types/types';
4+
import { ArchiveParams, AutomizerParams } from '../../types/types';
55
import IArchive, { ArchivedFile } from '../../interfaces/iarchive';
66
import { XmlDocument } from '../../types/xml-types';
77
import ArchiveJszip from './archive-jszip';
@@ -14,6 +14,7 @@ import {
1414
} from '../file-helper';
1515
import { vd } from '../general-helper';
1616
import extract from 'extract-zip';
17+
import { compressFolder } from '../jszip-helper';
1718

1819
export default class ArchiveFs extends Archive implements IArchive {
1920
archive: boolean;
@@ -143,18 +144,19 @@ export default class ArchiveFs extends Archive implements IArchive {
143144
}
144145
}
145146

146-
async output(location: string): Promise<void> {
147+
async output(location: string, params: AutomizerParams): Promise<void> {
147148
await this.writeBuffer(this);
149+
this.setOptions(params);
148150

149-
let exec = require('child_process').exec;
150-
const command = `(cd ${this.workDir} && zip -r - .) > ${location}`;
151-
const script = await exec(command);
151+
if (exists(location)) {
152+
await fsPromises.rm(location);
153+
}
152154

153-
script.on('exit', async (code) => {
154-
if (this.params.cleanupWorkDir === true) {
155-
await this.cleanupWorkDir();
156-
}
157-
});
155+
await compressFolder(this.workDir, location, this.options);
156+
157+
if (this.params.cleanupWorkDir === true) {
158+
await this.cleanupWorkDir();
159+
}
158160
}
159161

160162
async cleanupWorkDir(): Promise<void> {

src/helper/archive/archive-jszip.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import Archive from './archive';
2-
import fs from 'fs';
2+
import fs, { promises as fsp } from 'fs';
33
import JSZip, { InputType } from 'jszip';
44
import { AutomizerParams } from '../../types/types';
55
import IArchive, { ArchivedFile } from '../../interfaces/iarchive';
66
import { XmlDocument } from '../../types/xml-types';
7+
import path from 'path';
78

89
export default class ArchiveJszip extends Archive implements IArchive {
910
archive: JSZip;
1011
file: Buffer;
11-
options: JSZip.JSZipGeneratorOptions<'nodebuffer'> = {
12-
type: 'nodebuffer',
13-
};
1412

1513
constructor(filename) {
1614
super(filename);
@@ -93,15 +91,6 @@ export default class ArchiveJszip extends Archive implements IArchive {
9391
return (await this.archive.generateAsync(this.options)) as Buffer;
9492
}
9593

96-
private setOptions(params: AutomizerParams): void {
97-
if (params.compression > 0) {
98-
this.options.compression = 'DEFLATE';
99-
this.options.compressionOptions = {
100-
level: params.compression,
101-
};
102-
}
103-
}
104-
10594
async readXml(file: string): Promise<XmlDocument> {
10695
const isBuffered = this.fromBuffer(file);
10796

src/helper/archive/archive.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
22
import { ArchivedFile, ArchiveType } from '../../interfaces/iarchive';
33
import { XmlDocument } from '../../types/xml-types';
4+
import { AutomizerParams } from '../../types/types';
5+
import JSZip from 'jszip';
46

57
export default class Archive {
68
filename: string;
79
buffer: ArchivedFile[] = [];
10+
options: JSZip.JSZipGeneratorOptions<'nodebuffer'> = {
11+
type: 'nodebuffer',
12+
};
813

914
constructor(filename) {
1015
this.filename = filename;
@@ -42,4 +47,13 @@ export default class Archive {
4247
fromBuffer(relativePath) {
4348
return this.buffer.find((file) => file.relativePath === relativePath);
4449
}
50+
51+
setOptions(params: AutomizerParams): void {
52+
if (params.compression > 0) {
53+
this.options.compression = 'DEFLATE';
54+
this.options.compressionOptions = {
55+
level: params.compression,
56+
};
57+
}
58+
}
4559
}

src/helper/jszip-helper.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import fs, { promises as fsp } from 'fs';
2+
import path from 'path';
3+
import JSZip from 'jszip';
4+
5+
// Thanks to https://github.com/DesignByOnyx
6+
// see https://github.com/Stuk/jszip/issues/386 for more info
7+
8+
/**
9+
* Returns a flat list of all files and subfolders for a directory (recursively).
10+
* @param {string} dir
11+
* @returns {Promise<string[]>}
12+
*/
13+
const getFilePathsRecursively = async (dir) => {
14+
// returns a flat array of absolute paths of all files recursively contained in the dir
15+
const list = await fsp.readdir(dir);
16+
const statPromises = list.map(async (file) => {
17+
const fullPath = path.resolve(dir, file);
18+
const stat = await fsp.stat(fullPath);
19+
if (stat && stat.isDirectory()) {
20+
return getFilePathsRecursively(fullPath);
21+
}
22+
return fullPath;
23+
});
24+
25+
return (await Promise.all(statPromises)).flat(Infinity);
26+
};
27+
28+
/**
29+
* Creates an in-memory zip stream from a folder in the file system
30+
* @param {string} dir
31+
* @returns {JSZip}
32+
*/
33+
const createZipFromFolder = async (dir) => {
34+
const absRoot = path.resolve(dir);
35+
const filePaths = await getFilePathsRecursively(dir);
36+
return filePaths.reduce((z, filePath) => {
37+
const relative = filePath.replace(absRoot, '');
38+
// create folder trees manually :(
39+
const zipFolder = path
40+
.dirname(relative)
41+
.split(path.sep)
42+
.reduce((zf, dirName) => zf.folder(dirName), z);
43+
44+
zipFolder.file(path.basename(filePath), fs.createReadStream(filePath));
45+
return z;
46+
}, new JSZip());
47+
};
48+
49+
/**
50+
* Compresses a folder to the specified zip file.
51+
* @param {string} srcDir
52+
* @param {string} destFile
53+
*/
54+
export const compressFolder = async (srcDir, destFile, options) => {
55+
const start = Date.now();
56+
try {
57+
const zip = await createZipFromFolder(srcDir);
58+
zip
59+
.generateNodeStream({ streamFiles: true, ...options })
60+
.pipe(fs.createWriteStream(destFile))
61+
.on('error', (err) => console.error('Error writing file', err.stack))
62+
.on('finish', () =>
63+
console.log('Zip written successfully:', Date.now() - start, 'ms'),
64+
);
65+
} catch (ex) {
66+
console.error('Error creating zip', ex);
67+
}
68+
};

src/helper/modify-presentation-helper.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,12 @@ export default class ModifyPresentationHelper {
9999
await Tracker.collect('ppt/slideMasters', 'image', keepFiles);
100100
await Tracker.collect('ppt/slideLayouts', 'image', keepFiles);
101101

102-
const removed = await FileHelper.removeFromDirectory(
103-
archive,
104-
'ppt/media',
105-
(file) => {
106-
const info = FileHelper.getFileInfo(file.name);
107-
return (
108-
extensions.includes(info.extension.toLowerCase()) &&
109-
!keepFiles.includes(info.base)
110-
);
111-
},
112-
);
113-
vd(removed);
102+
await FileHelper.removeFromDirectory(archive, 'ppt/media', (file) => {
103+
const info = FileHelper.getFileInfo(file.name);
104+
return (
105+
extensions.includes(info.extension.toLowerCase()) &&
106+
!keepFiles.includes(info.base)
107+
);
108+
});
114109
}
115110
}

0 commit comments

Comments
 (0)