Building a Website with Scribble
The source code for the PRL website is written using Scribble, the Racket documentation tool. I am very happy with this choice, and you should be too!
The Story so Far
Last Fall, I took a flight to Chicago (on my way to RacketCon 2016). When I landed, there was a new message in my inbox:
Subject: Web Page
Date: 2016-09-15
You have been nominated webmaster by public acclamation. Congratulations!
Emboldened by the trust of my people, I promptly converted the PRL website from Racket-generating-HTML to the fine scribble/html
preprocessor language (commit a0600d
) This bold action polarized the community.
I can’t read the source anymore! Is this really an improvement?
Fear not, citizens. The switch to scribble/html
was the right choice, and you too can learn to read the source code.
How to Read scribble/html
Programs
Basics
Scribble is a language for writing Racket documentation. The key innovation in Scribble is the @-expression (read: “at expression”). The scribble/html
language combines @-expression syntax with functions that generate HTML.
@-syntax
Greg Hendershott and the Scribble Documentation explain @-expressions properly. Here’s a short tutorial (Part 1 of 2, “the basics”):
- Scribble programs start in “text mode”. Every character you type goes straight to the document you are building.
- The @-sign toggles to “Racket mode” for the next expression. In Racket mode, the characters you type will be evaluated as a Racket program to produce part of the document.
Examples: Evaluating "Hello Dave"
puts “Hello Dave” in your document. Evaluating "Hello @Dave"
puts “Hello ???” in your document, where "???" is the value of the variable Dave
. Finally if Dave
is the name of a function, then "Hello @(Dave)"
calls the Dave
function with zero arguments and puts whatever it returns into your document.
To make it easy to interleave text, function calls, and code, Scribble discriminates between 4 kinds of parentheses when they follow an @-sign (Part 2 of 2, “the parens”):
@(f A B)
is just like the function call(f A B)
in Racket@f[A B]
is the same as@(f A B)
, but typically more useful because …@f[A B]{....}
evaluates the....
in “text mode” to a list of wordsw*
, then callsf
just like(apply f A B w*)
@f{....}
evaluates the....
in “text mode” and callsf
with the results@f|{....}|
is similar, but the....
are in “unescapable text mode”
“Unescapable text mode” treats @-signs as text instead of toggling between modes.
Generating HTML
The scribble/html
language comes with functions that render HTML. These functions have the same name as the corresponding HTML tag.
Example program:
1 2 |
#lang scribble/html @p{Hello World} |
Running this program prints:
1 |
<p>Hello World</p> |
No surprises.
One thing that is surprising is how scribble/html
handles tag attributes. Every tag-rendering function accepts “Racket mode” arguments that specify an attribute name and attribute value.
For example:
1 2 |
#lang scribble/html @p[style: "color:red"]{Hello World} |
Prints:
1 |
<p style="color:red">Hello World</p> |
Hope the output looks familiar. The input syntax is strange, but that’s what it is.
Larger programs print larger webpages. Each page on the PRL website is HTML generated by one scribble/html
program.
Why scribble/html
is an Improvement
Before scribble/html
, the PRL website was implemented in scribble/text
. A scribble/text
program renders and prints text. There is no extra support for HTML.
To compare, here’s the start of the old homepage:
1 2 3 4 5 6 7 8 9 |
And here is the start of the scribble/html
’d homepage:
1 2 3 4 5 6 7 8 9 |
#lang scribble/html @require["templates.rkt"] @doctype{html} @html[lang: "en"]{ @header{Home} @body[id: "pn-top"]{ @navbar{Home} @div[class: "jumbotron"]{ |
The pages look similar. The new one has more @-signs and parentheses, the old one has more <
-signs and quotes. If you were able to edit the old page, you should be able to edit the new page.
The key improvement in the new page is that common mistakes are now compile-time errors.
-
Before, a typo like
<hmtl>
would generate an ugly webpage. After, a typo like@hmtl
is a syntax error. -
Before, a typo like
<b>....
with no closing tag would generate an ugly webpage. After, a typo like@b{....
is a syntax error.
Both flavors of error message come with source-code line numbers. This is very very helpful.
Small Improvements
1. More Functions
Before, the Teaching page contained some interesting HTML for rendering vertical text (look for the word “Semantics” to see how this was used):
1 |
<span class="how-to-design-programs">S<br />e<br />m<br />a<br />n<br />t<br />i<br />c<br />s<br /><br /></span> |
After, the same text is generated from a function call:
1 |
@span[class: "how-to-design-programs"]{@vertical-text{Semantics}} |
The vertical-text
function is simple:
1 2 3 4 |
@require[(only-in racket/list add-between)] @(define (vertical-text . str*) (add-between (string->list (append* str*)) (br))) |
2. More Structure, Less Boilerplate
Here’s part of the old definition of “Ben Greenman” on the People page:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<div class="row pn-person"> <div class="col-md-12 pn-row-eq-height"> <div class="col-md-3 pn-photo"> <div class="img-wrapper"> <img src="img/ben_greenman.jpg" title="Ben Greenman" alt="Ben Greenman" /> </div> </div> <div class="col-md-9"> <div class="col-md-4 pn-contact"> <span class="pn-name">Ben Greenman</span><br /> Advisor: Matthias Felleisen<br /> <a href="mailto:types@"@"ccs.neu.edu">types@"@"ccs.neu.edu</a><br /> <a href="http://www.ccs.neu.edu/home/types">www.ccs.neu.edu/home/types</a> </div> <div class="col-md-3 pn-muted col-md-offset-5"> Joined 2014 </div> <div class="col-md-12 pn-bio"> <p>I like constructions .... </p> </div> </div> </div> </div> |
The new definition uses a helper function with keyword arguments for each “field” of the person:
1 2 3 4 5 6 7 8 |
@person[#:name "Ben Greenman" #:title "Advisor: Matthias Felleisen" #:e-mail "types@ccs.neu.edu" #:website "http://ccs.neu.edu/home/types" #:history @list["Joined 2014"] #:img "ben_greenman.jpg"]{ I like constructions .... } |
3. Less String-Formatting
Before, the code did a lot of string formatting (link):
1 2 |
(define (link url body) (string-append "<a href=\"" url "\">" body "</a>")) |
The new code has no need for such helper functions.
1 |
@a[href: url body] |
Bottom Line
Scribble is a good language for making static HTML pages.
If you liked this post, you may also be interested in:
- Pollen
- RacketScript
- Other websites built using
scribble/html
: (1) nanopass.github.io (source code), (2) Gradual Typing Across the Spectrum (source code). - Notes from a Gradual Typing Across the Spectrum PI meeting