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/print.css" \
> "$css_temp" > "$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 \ concat_files \
"../shared/vendor/jszip.min.js" \
"../shared/vendor/docx-preview.min.js" \
"../shared/zddc.js" \ "../shared/zddc.js" \
"../shared/hash.js" \ "../shared/hash.js" \
"../shared/theme.js" \ "../shared/theme.js" \

View file

@ -53,12 +53,14 @@
return; 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') { if (typeof JSZip === 'undefined') {
// Dynamically load JSZip alert('JSZip library not bundled — rebuild archive with shared/vendor/jszip.min.js');
await loadJSZip(); return;
} }
const zip = new JSZip(); const zip = new JSZip();
const selectedFiles = []; const selectedFiles = [];
@ -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 // Show progress indicator
function showProgress(message, current, total) { function showProgress(message, current, total) {
let progressDiv = document.getElementById('progressIndicator'); let progressDiv = document.getElementById('progressIndicator');
@ -260,7 +251,6 @@
rowsToCSV, rowsToCSV,
exportCSV, exportCSV,
downloadSelected, downloadSelected,
loadJSZip,
showProgress, showProgress,
hideProgress, hideProgress,
downloadFile, downloadFile,

View file

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

View file

@ -25,8 +25,14 @@ concat_files \
"css/spreadsheet.css" \ "css/spreadsheet.css" \
> "$css_temp" > "$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 \ concat_files \
"../shared/vendor/jszip.min.js" \
"../shared/vendor/docx-preview.min.js" \
"../shared/zddc.js" \ "../shared/zddc.js" \
"../shared/hash.js" \ "../shared/hash.js" \
"../shared/theme.js" \ "../shared/theme.js" \

View file

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

View file

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ZDDC Classifier</title> <title>ZDDC Classifier</title>
<link rel="icon" type="image/svg+xml" href="{{FAVICON}}"> <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> <style>
{{CSS_PLACEHOLDER}} {{CSS_PLACEHOLDER}}
</style> </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/print.css" \
> "$css_temp" > "$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 \ concat_files \
"../shared/vendor/jszip.min.js" \
"../shared/vendor/docx-preview.min.js" \
"../shared/zddc.js" \ "../shared/zddc.js" \
"../shared/hash.js" \ "../shared/hash.js" \
"../shared/theme.js" \ "../shared/theme.js" \

View file

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