Front end: app.js ships its 137 kB of comments to every browser; serve a built copy and keep the commented source #1149

Closed
opened 2026-10-31 03:04:21 +00:00 by tiagoagueda · 0 comments
Owner

What exists today

src/postulo/static/js/app.js is 9,250 lines and 369,047 bytes, loaded on every page by templates/base.html:62 (<script src="{% static 'js/app.js' %}" defer>). Measured with a Python line scanner (whole-line // and /* ... */ comments; trailing comments not counted, so the real share is a little higher):

copy bytes gzip -9 Brotli
as committed 369,047 101,228 82,376
comment lines and blank lines removed 231,737 50,079 41,141
the same, indentation removed too 194,899 46,697

1,869 lines are comments and hold 136,773 bytes (37 per cent of the file); 537 more are blank. The comments are the project's documentation of the script and must stay in the source.

The ledger's figure ("halves the transfer") holds. What changes: WhiteNoise's manifest storage gives the file a hashed name and a far-future cache, so the saving is per release and per browser (a first visit, every deploy, every private window), not per page view. The scout's "per-page split later" is a separate and larger change and is not part of this one.

How compiled assets are handled today: app.css is built by npm run build:css (Tailwind), committed, and .forgejo/workflows/ci.yml:422-449 rebuilds it and fails on git diff --exit-code; the vendored scripts, icons and brand marks are checked the same way. No JavaScript build step exists and no minifier is in package.json or node_modules.

What reads app.js: about 20 unit test files read the source as text (tests/test_page_script.py:32, tests/test_board_fold.py:68,819 splits a function body out by "\n }\n", tests/test_template_lint.py:157,187 strips /* */ before searching, tests/security/test_offline_page.py:71, tests/test_keyboard_and_focus.py:30, and others). None reads line numbers or comments as such, but several depend on the source's layout, so they must go on reading the commented source. The CSP is script-src 'self' (config/settings/base.py:861) with no hashes, so a different file body changes nothing there.

What would change

  • Keep static/js/app.js as the source. Add esbuild (one binary devDependency, MIT) and a build:js script: esbuild assets/js/app.js --minify --outfile=src/postulo/static/js/app.min.js or, to keep names readable in the browser's debugger, --minify-whitespace --minify-syntax without renaming. The source should then move out of static/ (for example to assets/js/app.js, as the stylesheet's source lives in assets/css/) so that collectstatic does not publish both; the unit tests' path constant moves with it.
  • base.html:62 loads the built file.
  • ci.yml's asset check gains npm run build:js and a git diff --exit-code on the built file, with the same error line as app.css, so a stale built copy cannot reach main.
  • The Docker styles stage builds it too, as it builds the stylesheet, so image and commit stay byte for byte the same.
  • A source map is optional; it would let the browser show the commented source when debugging.

What it saves or protects

medium: the script's transfer drops from 101 kB to 50 kB gzip (82 kB to 41 kB Brotli) with comments stripped alone; once per release per browser, since the hashed name is cached for good

Accessibility

none for the change itself, which removes no code; the browser suite (tests/e2e/, among them test_unsaved_work.py, test_help.py, test_keyboard_* through the served page) must run against the built file so it is the file the guards cover

Decisions

  • Commit the built file and check it in CI, or build it only in the image? Recommended: commit and check it, the rule already applied to app.css, the icons and the vendored scripts; the application keeps running without Node.
  • Full minification or whitespace and comments only? Recommended: whitespace and syntax without renaming identifiers; it takes most of the saving (46.7 kB gzip measured for comments plus indentation) and leaves stack traces readable in a bug report.
  • Split the script per page? Recommended: not in this issue; measure again after the build step.

Found by the efficiency and reliability audit of 2026-10-30 (finding A1; index postulo/postulo#1108). Checked against main at daee30b15 (2026-10-30).

## What exists today `src/postulo/static/js/app.js` is 9,250 lines and 369,047 bytes, loaded on every page by `templates/base.html:62` (`<script src="{% static 'js/app.js' %}" defer>`). Measured with a Python line scanner (whole-line `//` and `/* ... */` comments; trailing comments not counted, so the real share is a little higher): | copy | bytes | gzip -9 | Brotli | | --- | --- | --- | --- | | as committed | 369,047 | 101,228 | 82,376 | | comment lines and blank lines removed | 231,737 | 50,079 | 41,141 | | the same, indentation removed too | 194,899 | 46,697 | | 1,869 lines are comments and hold 136,773 bytes (37 per cent of the file); 537 more are blank. The comments are the project's documentation of the script and must stay in the source. The ledger's figure ("halves the transfer") holds. What changes: WhiteNoise's manifest storage gives the file a hashed name and a far-future cache, so the saving is per release and per browser (a first visit, every deploy, every private window), not per page view. The scout's "per-page split later" is a separate and larger change and is not part of this one. How compiled assets are handled today: `app.css` is built by `npm run build:css` (Tailwind), committed, and `.forgejo/workflows/ci.yml:422-449` rebuilds it and fails on `git diff --exit-code`; the vendored scripts, icons and brand marks are checked the same way. No JavaScript build step exists and no minifier is in `package.json` or `node_modules`. What reads app.js: about 20 unit test files read the source as text (`tests/test_page_script.py:32`, `tests/test_board_fold.py:68,819` splits a function body out by `"\n }\n"`, `tests/test_template_lint.py:157,187` strips `/* */` before searching, `tests/security/test_offline_page.py:71`, `tests/test_keyboard_and_focus.py:30`, and others). None reads line numbers or comments as such, but several depend on the source's layout, so they must go on reading the commented source. The CSP is `script-src 'self'` (`config/settings/base.py:861`) with no hashes, so a different file body changes nothing there. ## What would change - Keep `static/js/app.js` as the source. Add `esbuild` (one binary devDependency, MIT) and a `build:js` script: `esbuild assets/js/app.js --minify --outfile=src/postulo/static/js/app.min.js` or, to keep names readable in the browser's debugger, `--minify-whitespace --minify-syntax` without renaming. The source should then move out of `static/` (for example to `assets/js/app.js`, as the stylesheet's source lives in `assets/css/`) so that collectstatic does not publish both; the unit tests' path constant moves with it. - `base.html:62` loads the built file. - `ci.yml`'s asset check gains `npm run build:js` and a `git diff --exit-code` on the built file, with the same error line as `app.css`, so a stale built copy cannot reach `main`. - The Docker `styles` stage builds it too, as it builds the stylesheet, so image and commit stay byte for byte the same. - A source map is optional; it would let the browser show the commented source when debugging. ## What it saves or protects medium: the script's transfer drops from 101 kB to 50 kB gzip (82 kB to 41 kB Brotli) with comments stripped alone; once per release per browser, since the hashed name is cached for good ## Accessibility none for the change itself, which removes no code; the browser suite (`tests/e2e/`, among them `test_unsaved_work.py`, `test_help.py`, `test_keyboard_*` through the served page) must run against the built file so it is the file the guards cover ## Decisions - **Commit the built file and check it in CI, or build it only in the image?** Recommended: commit and check it, the rule already applied to `app.css`, the icons and the vendored scripts; the application keeps running without Node. - **Full minification or whitespace and comments only?** Recommended: whitespace and syntax without renaming identifiers; it takes most of the saving (46.7 kB gzip measured for comments plus indentation) and leaves stack traces readable in a bug report. - **Split the script per page?** Recommended: not in this issue; measure again after the build step. --- Found by the efficiency and reliability audit of 2026-10-30 (finding A1; index postulo/postulo#1108). Checked against `main` at `daee30b15` (2026-10-30).
tiagoagueda added this to the 0.6.0 milestone 2026-10-31 03:04:21 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#1149
No description provided.