perf(tools): vendor jszip + docx-preview for archive/transmittal/classifier

Same pattern as the browse fix. archive, transmittal, classifier
previously CDN-loaded jszip + docx-preview on first preview of a
.zip / .docx file via shared/preview-lib.js's loadLibrary helper.
That meant each first-preview blocked on a CDN round-trip + parse,
and broke entirely under restrictive networks or CSPs.

Vendor both libs under shared/vendor/ and concat them at the top of
each tool's build, ahead of init.js. window.JSZip + window.docx are
now defined immediately on page load. Drop the redundant loadLibrary
calls (and classifier's stray <script src="cdn..."> tag in the
template, plus archive's bespoke loadJSZip helper in export.js).

xlsx (SheetJS) intentionally stays CDN-loaded — at ~900 KB it's too
large to inline, and only fires on .xlsx preview which is a rarer
path.

Bundle size impact (uncompressed):
  archive:     304 KB → 476 KB  (+172 KB)
  transmittal: 449 KB → 621 KB  (+172 KB)
  classifier:  252 KB → 424 KB  (+172 KB)

With the gzip middleware (~75% reduction on HTML) and ETag-cached
revalidation now in place, the wire-size delta is ~40 KB per tool
on the first load and 0 on every subsequent load until redeploy.
This commit is contained in:
ZDDC 2026-05-03 23:34:28 -05:00
parent 50dd8f9bda
commit 9481122570
9 changed files with 42 additions and 31 deletions

View file

@ -27,8 +27,14 @@ concat_files \
"css/print.css" \
> "$css_temp"
# JavaScript files to concatenate in order
# JavaScript files to concatenate in order. Vendored libraries first
# (jszip, docx-preview) so window.JSZip + window.docx are defined before
# any tool code runs — replaces the previous CDN loadLibrary() calls in
# table.js + export.js. xlsx is intentionally still CDN-loaded on demand
# (~900 KB; too large to inline).
concat_files \
"../shared/vendor/jszip.min.js" \
"../shared/vendor/docx-preview.min.js" \
"../shared/zddc.js" \
"../shared/hash.js" \
"../shared/theme.js" \

View file

@ -53,10 +53,12 @@
return;
}
// Check if JSZip is loaded
// JSZip is vendored (concat'd by build.sh), so window.JSZip is
// already defined. Defensive check in case a future refactor
// reorders things.
if (typeof JSZip === 'undefined') {
// Dynamically load JSZip
await loadJSZip();
alert('JSZip library not bundled — rebuild archive with shared/vendor/jszip.min.js');
return;
}
const zip = new JSZip();
@ -123,17 +125,6 @@
}
}
// Load JSZip library dynamically
function loadJSZip() {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// Show progress indicator
function showProgress(message, current, total) {
let progressDiv = document.getElementById('progressIndicator');
@ -260,7 +251,6 @@
rowsToCSV,
exportCSV,
downloadSelected,
loadJSZip,
showProgress,
hideProgress,
downloadFile,

View file

@ -609,9 +609,9 @@
if (!container) return;
try {
await loadLibrary('https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js');
await loadLibrary('https://cdn.jsdelivr.net/npm/docx-preview@latest/dist/docx-preview.min.js');
// jszip + docx-preview are vendored (concatenated by build.sh
// ahead of every tool module), so window.JSZip and window.docx
// are already defined here.
const arrayBuffer = await (file.handle
? file.handle.getFile().then(f => f.arrayBuffer())
: fetch(file.url).then(r => r.arrayBuffer()));

View file

@ -25,8 +25,14 @@ concat_files \
"css/spreadsheet.css" \
> "$css_temp"
# JavaScript files to concatenate in order
# JavaScript files to concatenate in order. Vendored libraries first
# (jszip, docx-preview) so window.JSZip + window.docx are defined before
# any tool code runs. Replaces the previous <script src="cdn..."> tag in
# template.html plus the loadLibrary CDN calls in preview.js. xlsx stays
# CDN-loaded on demand (~900 KB; too large to inline).
concat_files \
"../shared/vendor/jszip.min.js" \
"../shared/vendor/docx-preview.min.js" \
"../shared/zddc.js" \
"../shared/hash.js" \
"../shared/theme.js" \

View file

@ -383,9 +383,7 @@
if (!container) return;
try {
await loadLibrary('https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js');
await loadLibrary('https://cdn.jsdelivr.net/npm/docx-preview@latest/dist/docx-preview.min.js');
// jszip + docx-preview vendored by build.sh — already in scope.
const blob = await getFileBlob(file);
const arrayBuffer = await blob.arrayBuffer();

View file

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ZDDC Classifier</title>
<link rel="icon" type="image/svg+xml" href="{{FAVICON}}">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<style>
{{CSS_PLACEHOLDER}}
</style>

8
shared/vendor/docx-preview.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -36,8 +36,13 @@ concat_files \
"css/print.css" \
> "$css_temp"
# JavaScript files to concatenate in order
# JavaScript files to concatenate in order. Vendored libraries first
# (jszip, docx-preview) so window.JSZip + window.docx are defined before
# any tool code runs — replaces the previous CDN loadLibrary() calls
# scattered through files-preview.js. xlsx stays CDN-loaded on demand.
concat_files \
"../shared/vendor/jszip.min.js" \
"../shared/vendor/docx-preview.min.js" \
"../shared/zddc.js" \
"../shared/hash.js" \
"../shared/theme.js" \

View file

@ -200,8 +200,7 @@
return;
}
try {
await loadLibrary('https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js');
await loadLibrary('https://cdn.jsdelivr.net/npm/docx-preview@latest/dist/docx-preview.min.js');
// jszip + docx-preview vendored by build.sh — already in scope.
var arrayBuffer = await getFileArrayBuffer(file);
container.innerHTML = '';
await window.docx.renderAsync(arrayBuffer, container);
@ -474,7 +473,7 @@
}
try {
updatePreviewStatus('Loading ZIP...');
await loadLibrary('https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js');
// JSZip vendored by build.sh — already in scope.
var arrayBuffer = await zipFile.arrayBuffer();
var zip = await JSZip.loadAsync(arrayBuffer);
var sourceEntries = [];