Learning to <code/>
update: I've since moved to using Shiki for my syntax highlighting since it is built into Astro and can render to static html. The rest of the article is still relevant
This is a coding blog, so it makes sense to start out by making sure we can
represent code. My site:developer.mozilla.org
-enhanced googling
skills tell me that
<pre />
+ <code />
is all the rage, so let's give that a try.
The Basics
if ( window.navigator ) {
console.log("Hello, user!")
} else {
console.error("There's nobody to read me :(")
}
That looks good for vanilla Javascript, but what about JSX? The block above actually looks like
<pre>
<code>{ stripIndent`
if ( window.navigator ) {
console.log("Hello, user!")
} else {
console.error("There's nobody to read me :(")
}
`}</code>
</pre>
stripIndent`...`
is a
Tagged Template Literal
and allows us to write our code at the current indent level without the spacing
making it into the final doc, while still maintaining its internal indentation.
Super neat!
In a similar vein, that block actually looks like
<pre>
<code>{ html`
...
`}</code>
</pre>
Where html`...`
is a similar tagged template which helps us automatically
encode our alligators into their html-escaped equivalent. Looking alright so
far...
Syntax Highlighting
I'm a strong believer in good web fundamentals, so any feature enhancement we add must have reasonable fallback behavior for people who choose to turn off Javascript or even who use browsers (such as links) who don't even support the notion. But I also can't stand reading un-highlighted code, and I'm sure you all would appreciate some color in your life as well. Let's see what we can do about that.
The first result for "react syntax highlighter" is, unsurprisingly, react-syntax-highlighter
, which seems to support progressively
highlighting if the client has JS enabled.
if ( window.navigator ) {
console.log("Hello, user!")
} else {
console.error("There's nobody to read me :(")
}
Not bad! In JSX, the above looks like
/* Contains our 2 styles behind a @media prefers-color-scheme selector */
import '../highlight.css'
// Then, anywhere
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
<SyntaxHighlighter language="javascript" useInlineStyles={false}>{ stripIndent`
if ( window.navigator ) {
console.log("Hello, user!")
} else {
console.error("There's nobody to read me :(")
}
`}</SyntaxHighlighter>
Both of which render from the server as completely valid <pre></pre> blocks with JS only being required to tag and color them.
Thinking outside the box
That scrollbar doesn't looks very good, considering how much free real estate we're wasting in our gutters. Let's see if we can't get something akin to 'full-bleed' working.
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
<SyntaxHighlighter language="javascript" useInlineStyles={false}>{ stripIndent`
if ( window.navigator ) {
console.log("Hello, user!")
} else {
console.error("There's nobody to read me :(")
}
`}</SyntaxHighlighter>
And, just to test, if our content is really long...
if(you_use_really_long_variable_names_and_hate_newlines_because_youre_a_l33t_hacker_man){console.log("what a cool guy")}
This all works because of the amazing CSS Grid, which is nice enough to let us break out of the column we're constrained to as will. In its entirety, the code that allows all of this to work is as follows
/* styles.css */
main {
display: grid;
grid-template-columns:
1ch
1fr
min(65ch, calc(100% - 2ch))
1fr
1ch
;
--column-content: 3;
--column-full: 2 / -2;
}
main > * {
grid-column: var(--column-content);
}
/* blog.module.css */
.article {
display: grid;
grid-template-columns: subgrid;
/* set OURSELVES to capture the soft-gutter */
grid-column: var(--column-full);
& > * {
/* Tell OUR children to default to the new content column */
--column-content: 2;
--column-full: 1 / -1;
grid-column: var(--column-content);
}
}
.full_bleed {
width: 100%;
grid-column: var(--column-full);
display: flex;
justify-content: center;
}