Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Highlight with Prism in a worker and gzip text responses

306f36fcef89c145abcd0c5331453d0b179f37ce · Keenan Nemetz · 12d ago · parent e5542ac6

Highlight with Prism in a worker and gzip text responses

The code browser now names a blob's language and lets the browser colour it.
Chroma is gone: server/web/highlight.go is an extension table, and
static/prism-worker.js runs Prism — every language Prism ships — off the main
thread. The server sends escaped lines with their numbers and L# anchors, so a
page is complete and scrollable before any script runs and the worker only swaps
the contents of each line cell. The table covers 186 languages; a file it does
not claim stays plain rather than being coloured by a guess.

The bundle those grammars make is 545 KB, so the instance now gzips text
responses itself instead of relying on a proxy, and static assets carry a
content ETag so a browser revalidates with a 304 rather than re-downloading the
bundle on every code page. The raw-download route opts out with
Cache-Control: no-transform because it promises an exact Content-Length.

This also makes JavaScript a requirement rather than a nicety: the label and
assignee token inputs lose their noscript save buttons and their comma-separated
text fallback, and the docs say so.

Measured: release binary 26.08 MB -> 22.70 MB; /r/{repo}/code 11.1 KB -> 2.6 KB
over the wire; the Prism bundle 545 KB -> 206 KB gzipped, fetched once.

scripts/vendor-prism.py regenerates the bundle from Prism's components.json
require graph, and --check fails if the committed assets drift.

server/web/static/prism-worker.js +65

@@ -0,0 +1,65 @@
1 /* Highlights one blob, off the main thread.
2 *
3 * Prism is string-only: given text and a grammar it returns HTML, so the whole
4 * job — tokenise, split into lines, send the lines back — runs here and a large
5 * file never blocks the page. The page keeps its server-rendered line table and
6 * swaps in the contents of each line cell, so line numbers and anchors are
7 * present before this file runs and stay put afterwards.
8 *
9 * Prism's core detects a WorkerGlobalScope, but `manual` keeps it from also
10 * trying to walk a document that does not exist. */
11
12 self.Prism = { manual: true };
13 importScripts('prism.min.js', 'prism-languages.min.js');
14
15 // splitLines closes the spans that are open at a newline and reopens them on the
16 // next line, so a multi-line comment or string keeps its class across lines
17 // without one line's span swallowing the next. Prism escapes markup, so the only
18 // tags in its output are its own spans.
19 function splitLines(html) {
20 const lines = [];
21 const open = [];
22 let line = '';
23 const tag = /<span[^>]*>|<\/span>|[^<]+/g;
24 let match;
25 while ((match = tag.exec(html)) !== null) {
26 const part = match[0];
27 if (part.charAt(0) === '<') {
28 if (part.charAt(1) === '/') open.pop();
29 else open.push(part);
30 line += part;
31 continue;
32 }
33 const pieces = part.split('\n');
34 for (let i = 0; i < pieces.length; i++) {
35 if (i > 0) {
36 line += '</span>'.repeat(open.length);
37 lines.push(line);
38 line = open.join('');
39 }
40 line += pieces[i];
41 }
42 }
43 lines.push(line);
44 return lines;
45 }
46
47 self.onmessage = (event) => {
48 const job = event.data || {};
49 const grammar = self.Prism.languages[job.lang];
50 if (!grammar || typeof job.code !== 'string') {
51 // An unknown language is not an error: the page keeps the plain text it was
52 // served with.
53 self.postMessage({ id: job.id, lines: null });
54 return;
55 }
56 let lines = null;
57 try {
58 lines = splitLines(self.Prism.highlight(job.code, grammar, job.lang));
59 } catch (err) {
60 // A grammar that throws on this input must not take the page's highlighting
61 // down with it: answer plain, as if the language were unknown.
62 lines = null;
63 }
64 self.postMessage({ id: job.id, lines });
65 };