diff --git a/src/virtual-machine.js b/src/virtual-machine.js index e02d87222a2..14ff8310a2f 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -537,6 +537,22 @@ class VirtualMachine extends EventEmitter { file.date = date; } + // Tell JSZip to only compress file formats where there will be a significant gain. + const COMPRESSABLE_FORMATS = [ + '.json', + '.svg', + '.wav', + '.ttf', + '.otf' + ]; + for (const file of Object.values(zip.files)) { + if (COMPRESSABLE_FORMATS.some(ext => file.name.endsWith(ext))) { + file.options.compression = 'DEFLATE'; + } else { + file.options.compression = 'STORE'; + } + } + return zip; } @@ -546,9 +562,9 @@ class VirtualMachine extends EventEmitter { */ saveProjectSb3 (type) { return this._saveProjectZip().generateAsync({ + // Don't configure compression here. _saveProjectZip() will set it for each file. type: type || 'blob', - mimeType: 'application/x.scratch.sb3', - compression: 'DEFLATE' + mimeType: 'application/x.scratch.sb3' }); } diff --git a/test/fixtures/tw-mixed-file-formats.sb3 b/test/fixtures/tw-mixed-file-formats.sb3 new file mode 100644 index 00000000000..f06c40d680b Binary files /dev/null and b/test/fixtures/tw-mixed-file-formats.sb3 differ diff --git a/test/integration/tw_compression_per_file_type.js b/test/integration/tw_compression_per_file_type.js new file mode 100644 index 00000000000..35f02a68cdb --- /dev/null +++ b/test/integration/tw_compression_per_file_type.js @@ -0,0 +1,31 @@ +const {test} = require('tap'); +const fs = require('fs'); +const path = require('path'); +const VM = require('../../src/virtual-machine'); +const makeTestStorage = require('../fixtures/make-test-storage'); +const JSZip = require('@turbowarp/jszip'); + +test('saveProjectSb3() per-file compression', t => { + const vm = new VM(); + vm.attachStorage(makeTestStorage()); + const fixture = fs.readFileSync(path.join(__dirname, '../fixtures/tw-mixed-file-formats.sb3')); + vm.loadProject(fixture) + .then(() => vm.saveProjectSb3('arraybuffer')) + .then(buffer => JSZip.loadAsync(buffer)) + .then(zip => { + const isCompressed = pathInZip => { + // note: uses JSZip private APIs, not very stable, but it should be okay... + const file = zip.files[pathInZip]; + return file._data.compression.magic === '\x08\x00'; + }; + + t.ok(isCompressed('project.json')); + t.ok(isCompressed('5cb46ddd903fc2c9976ff881df9273c9.wav')); + t.ok(isCompressed('cd21514d0531fdffb22204e0ec5ed84a.svg')); + + t.notOk(isCompressed('0b2e50ca4107ce57416e2ceb840a6347.jpg')); + t.notOk(isCompressed('5c8826d846c06dddeb77590e8792fb7d.png')); + + t.end(); + }); +});