You could use JSZip and FileSaver in order to achieve this. Note that the files are ordered alphabetically in the input
so this snippet requires your files to be named accordingly. I have used an input
in order to make testing easier, you could easily adapt it to axios requests for each file.
The snippet actually doesn't work on this web page because the downloads are disallowed by the snippet iframe's policy so you have to try it by other means.
const objectWithoutProperties = (obj, keys) => {
const target = {};
for (let i in obj) {
if (keys.indexOf(i) >= 0) continue;
if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
target[i] = obj[i];
}
return target;
}
const mergeFiles = (a, b) => {
if (!a) return b;
const intersect = Object.keys(a.files).filter((x) =>
Object.keys(b.files).includes(x)
);
a.files = { ...a.files, ...objectWithoutProperties(b.files, intersect) };
return Promise.all(
intersect.map((file) =>
Promise.all([
a.file(file).async("arraybuffer"),
b.file(file).async("arraybuffer"),
])
.then((x) => new Blob(x))
.then((x) => a.file(file, x))
)
).then(() => a);
};
const MultipleZipMerger = () => {
function change(e) {
Promise.all([...e.target.files].map(JSZip.loadAsync))
.then((x) =>
x.reduce(
(acc, cur) => acc.then((x) => mergeFiles(x, cur)),
Promise.resolve()
)
)
.then((x) => x.generateAsync({ type: "blob" }))
.then((x) => saveAs(x, "hello.zip"));
}
return <input onChange={change} type="file" multiple />;
};
ReactDOM.render(<MultipleZipMerger />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script>
<div id="root"></div>
This is a standalone version of this code which you can test as a local file:
<!doctype html>
<html>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script>
<div id="root"></div>
<script>
const objectWithoutProperties = (obj, keys) => {
const target = {};
for (let i in obj) {
if (keys.indexOf(i) >= 0) continue;
if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
target[i] = obj[i];
}
return target;
}
const mergeFiles = (a, b) => {
if (!a) return b;
const intersect = Object.keys(a.files).filter((x) =>
Object.keys(b.files).includes(x)
);
a.files = { ...a.files, ...objectWithoutProperties(b.files, intersect) };
return Promise.all(
intersect.map((file) =>
Promise.all([
a.file(file).async("arraybuffer"),
b.file(file).async("arraybuffer"),
])
.then((x) => new Blob(x))
.then((x) => a.file(file, x))
)
).then(() => a);
};
const MultipleZipMerger = () => {
function change(e) {
Promise.all([...e.target.files].map(JSZip.loadAsync))
.then((x) =>
x.reduce(
(acc, cur) => acc.then((x) => mergeFiles(x, cur)),
Promise.resolve()
)
)
.then((x) => x.generateAsync({ type: "blob" }))
.then((x) => saveAs(x, "hello.zip"));
}
return React.createElement("input", {
onChange: change,
type: "file",
multiple: true,
});
};
ReactDOM.render(
React.createElement(MultipleZipMerger, null),
document.getElementById("root")
);
</script>
</body>
</html>