Skip to content

Commit e5c1e4e

Browse files
committed
Add a second post!
1 parent 02f47cc commit e5c1e4e

7 files changed

+196
-95
lines changed

gatsby-node.js

Lines changed: 92 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ exports.createPages = async ({ graphql, actions }) => {
5757
frontmatter {
5858
globalPreamble,
5959
lib,
60+
template,
6061
preambles {
6162
file,
6263
text
@@ -67,97 +68,110 @@ exports.createPages = async ({ graphql, actions }) => {
6768
}
6869
}
6970
`);
71+
7072
const grammar = await getTmRegistry(ssrFileProvider).loadGrammar('source.tsx');
7173
const tmTokenizer = createTmGrammarTokenizer({ grammar });
7274
if (result.errors) {
7375
throw new Error(result.errors.join('\n'));
7476
}
7577

7678
return result.data.allMarkdownRemark.edges.map(async ({ node }) => {
77-
const sourceFileContext = {};
78-
const codeBlockContext = {};
79-
visit(
80-
node.htmlAst,
81-
node => node.tagName === 'code' && ['ts', 'tsx'].includes(node.properties.dataLang),
82-
code => {
83-
const codeBlockId = code.properties.id;
84-
const metaData = deserializeAttributes(code.properties);
85-
const fileName = metaData.name || `/${codeBlockId}.${metaData.lang}`;
86-
const text = code.children[0].value.trim();
87-
const codeBlock = { text, fileName, id: codeBlockId };
88-
codeBlockContext[codeBlockId] = codeBlock;
89-
const sourceFileFragment = { codeBlockId };
90-
const existingSourceFile = sourceFileContext[fileName];
91-
if (existingSourceFile) {
92-
const { codeBlockId: prevCodeBlockId } = existingSourceFile.fragments[existingSourceFile.fragments.length - 1];
93-
const prevCodeBlock = codeBlockContext[prevCodeBlockId];
94-
codeBlock.start = prevCodeBlock.end + 1;
95-
codeBlock.end = codeBlock.start + text.length;
96-
existingSourceFile.fragments.push(sourceFileFragment);
97-
} else {
98-
const filePreamble = node.frontmatter.preambles.find(p => p.file === fileName);
99-
const preamble = (node.frontmatter.globalPreamble || '') + (filePreamble ? filePreamble.text : '');
100-
codeBlock.start = preamble.length;
101-
codeBlock.end = codeBlock.start + text.length;
102-
sourceFileContext[fileName] = {
103-
fragments: [sourceFileFragment],
104-
preamble,
105-
};
79+
if (node.frontmatter.template === 'CodePost') {
80+
const sourceFileContext = {};
81+
const codeBlockContext = {};
82+
visit(
83+
node.htmlAst,
84+
node => node.tagName === 'code' && ['ts', 'tsx'].includes(node.properties.dataLang),
85+
code => {
86+
const codeBlockId = code.properties.id;
87+
const metaData = deserializeAttributes(code.properties);
88+
const fileName = metaData.name || `/${codeBlockId}.${metaData.lang}`;
89+
const text = code.children[0].value.trim();
90+
const codeBlock = { text, fileName, id: codeBlockId };
91+
codeBlockContext[codeBlockId] = codeBlock;
92+
const sourceFileFragment = { codeBlockId };
93+
const existingSourceFile = sourceFileContext[fileName];
94+
if (existingSourceFile) {
95+
const { codeBlockId: prevCodeBlockId } = existingSourceFile.fragments[existingSourceFile.fragments.length - 1];
96+
const prevCodeBlock = codeBlockContext[prevCodeBlockId];
97+
codeBlock.start = prevCodeBlock.end + 1;
98+
codeBlock.end = codeBlock.start + text.length;
99+
existingSourceFile.fragments.push(sourceFileFragment);
100+
} else {
101+
const filePreamble = node.frontmatter.preambles.find(p => p.file === fileName);
102+
const preamble = (node.frontmatter.globalPreamble || '') + (filePreamble ? filePreamble.text : '');
103+
codeBlock.start = preamble.length;
104+
codeBlock.end = codeBlock.start + text.length;
105+
sourceFileContext[fileName] = {
106+
fragments: [sourceFileFragment],
107+
preamble,
108+
};
109+
}
106110
}
107-
}
108-
);
111+
);
109112

110-
const sourceFiles = new Map(Object.keys(sourceFileContext).map(fileName => {
111-
const context = sourceFileContext[fileName];
112-
const fullText = context.preamble + context.fragments.map(f => codeBlockContext[f.codeBlockId].text).join('\n');
113-
/** @type [string, string] */
114-
const entry = [fileName, fullText];
115-
return entry;
116-
}));
113+
const sourceFiles = new Map(Object.keys(sourceFileContext).map(fileName => {
114+
const context = sourceFileContext[fileName];
115+
const fullText = context.preamble + context.fragments.map(f => codeBlockContext[f.codeBlockId].text).join('\n');
116+
/** @type [string, string] */
117+
const entry = [fileName, fullText];
118+
return entry;
119+
}));
117120

118-
const extraLibFiles = await getExtraLibFiles(node.frontmatter.lib, lib);
119-
const system = createSystem(new Map([
120-
...Array.from(sourceFiles.entries()),
121-
...Array.from(lib.core.entries()),
122-
...Array.from(extraLibFiles.entries()),
123-
]));
124-
const { languageService } = createVirtualTypeScriptEnvironment(system, Array.from(sourceFiles.keys()));
125-
Object.keys(codeBlockContext).forEach(codeBlockId => {
126-
const codeBlock = codeBlockContext[codeBlockId];
127-
const { fileName } = codeBlock;
128-
const typeScriptTokenizer = createTypeScriptTokenizer({
129-
fileName,
130-
languageService,
131-
visibleSpan: { start: codeBlock.start, length: codeBlock.end - codeBlock.start },
132-
});
133-
const tokenizer = composeTokenizers(tmTokenizer, typeScriptTokenizer);
134-
codeBlock.tokens = tokenizer.tokenizeDocument(codeBlock.text);
135-
codeBlock.quickInfo = {};
136-
codeBlock.tokens.forEach(line => {
137-
line.tokens.forEach(token => {
138-
switch (token.type) {
139-
case TypeScriptTokenType.Identifier:
140-
codeBlock.quickInfo[token.sourcePosition] = languageService.getQuickInfoAtPosition(
141-
fileName,
142-
token.sourcePosition
143-
);
144-
}
121+
const extraLibFiles = await getExtraLibFiles(node.frontmatter.lib, lib);
122+
const system = createSystem(new Map([
123+
...Array.from(sourceFiles.entries()),
124+
...Array.from(lib.core.entries()),
125+
...Array.from(extraLibFiles.entries()),
126+
]));
127+
const { languageService } = createVirtualTypeScriptEnvironment(system, Array.from(sourceFiles.keys()));
128+
Object.keys(codeBlockContext).forEach(codeBlockId => {
129+
const codeBlock = codeBlockContext[codeBlockId];
130+
const { fileName } = codeBlock;
131+
const typeScriptTokenizer = createTypeScriptTokenizer({
132+
fileName,
133+
languageService,
134+
visibleSpan: { start: codeBlock.start, length: codeBlock.end - codeBlock.start },
135+
});
136+
const tokenizer = composeTokenizers(tmTokenizer, typeScriptTokenizer);
137+
codeBlock.tokens = tokenizer.tokenizeDocument(codeBlock.text);
138+
codeBlock.quickInfo = {};
139+
codeBlock.tokens.forEach(line => {
140+
line.tokens.forEach(token => {
141+
switch (token.type) {
142+
case TypeScriptTokenType.Identifier:
143+
codeBlock.quickInfo[token.sourcePosition] = languageService.getQuickInfoAtPosition(
144+
fileName,
145+
token.sourcePosition
146+
);
147+
}
148+
});
145149
});
146150
});
147-
});
148151

149-
actions.createPage({
150-
path: node.fields.slug,
151-
component: path.resolve(`./src/templates/Post.tsx`),
152-
context: {
153-
// Data passed to context is available
154-
// in page queries as GraphQL variables.
155-
slug: node.fields.slug,
156-
codeBlocks: codeBlockContext,
157-
sourceFiles: sourceFileContext,
158-
},
159-
});
160-
languageService.dispose();
152+
actions.createPage({
153+
path: node.fields.slug,
154+
component: path.resolve(`./src/templates/CodePost.tsx`),
155+
context: {
156+
// Data passed to context is available
157+
// in page queries as GraphQL variables.
158+
slug: node.fields.slug,
159+
codeBlocks: codeBlockContext,
160+
sourceFiles: sourceFileContext,
161+
},
162+
});
163+
languageService.dispose();
164+
} else {
165+
actions.createPage({
166+
path: node.fields.slug,
167+
component: path.resolve(`./src/templates/${node.frontmatter.template}.tsx`),
168+
context: {
169+
// Data passed to context is available
170+
// in page queries as GraphQL variables.
171+
slug: node.fields.slug,
172+
},
173+
});
174+
}
161175
});
162176
};
163177

package-lock.json

Lines changed: 30 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

posts/2019-01-19_Expressive-React-Component-APIs-with-Discriminated-Unions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: "Expressive React Component APIs with Discriminated Unions"
33
date: "2019-01-19"
44
slug: "expressive-react-component-apis-with-discriminated-unions"
55
globalPreamble: "declare var onChange: () => void;declare var console: { log: Function };\n"
6+
template: CodePost
67
preambles:
78
- file: select-1.tsx
89
text: "import React from 'react';\n"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
title: "Overengineering A Blog, Prologue: An Ode To Red Squigglies"
3+
date: "2019-02-06"
4+
template: PlainPost
5+
---
6+
7+
It’s all about the red squigglies. Red squigglies in Microsoft products have been saving me from looking stupid since the second grade, and now they save me from writing stupid code on a minutely basis. Well, significantly less stupid code. You know what I’m talking about. The red squigglies that tell you that no, it’s not spelled “bureauacracy,” and more frequently for me, that object is possibly `undefined`, dummy.
8+
9+
And you _want_ the red squigglies. They might not feel like your friend in the moment, but you’d much rather them get on your case than have them drop the ball on you. Whenever I’m involved in an incident review at work, one of the things I look for is why red squigglies didn’t save us. If we wrote bad code, why are we catching it at runtime instead of at compile now? Ensuring red squigglies appear where they’re supposed to has consequential impact. It keeps the site running for our customers.
10+
11+
I had been meaning to write my [first technical blog post](/expressive-react-component-apis-with-discriminated-unions) for a while, and I always figured that when I finally got around to writing it, I would publish it on Medium and be done with it. I had every intention of doing exactly that, until I thought about how I would explain the content to a colleague. Red squigglies. You want to write React component APIs that make red squigglies appear when someone tries to use your component wrong. That’s the whole point.
12+
13+
So, this blog was born, not because I’m dedicated to blogging frequently, but because if I’m going to do it even once, I’m not going to write a whole post about how to get more red squigglies _without showing you the red squigglies_. Every code sample in this blog is interactive. You can hover identifiers to see their type information; you can hover incorrect code to see the compiler errors; you can even edit the samples and watch how those change. You can fix the broken code samples or break the working code samples. I learn best through hands-on experiences, and thought others might too.
14+
15+
I think it came out pretty cool, and I learned a ton while building it. This was also the first time I’ve _started_ a serious frontend web project from scratch in a couple years, so I got to catch up on all the new frontend fads the kids are talking about on Twitter these days. So, my next couple posts will be dedicated to how I built this, what I learned while building it, and why I decided to compromise and make my squigglies straight lines.

src/pages/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export interface IndexPageProps {
2828

2929
export const query = graphql`
3030
query {
31-
allMarkdownRemark {
31+
allMarkdownRemark(sort: {
32+
fields: [frontmatter___date]
33+
order: DESC
34+
}) {
3235
totalCount
3336
edges {
3437
node {
@@ -51,7 +54,7 @@ const IndexPage = React.memo<IndexPageProps>(({ data }) => {
5154
return (
5255
<Layout>
5356
<SEO title="All Posts" />
54-
<Spacer vertical space={2}>
57+
<Spacer vertical space={1}>
5558
{data.allMarkdownRemark.edges.map(({ node }) => (
5659
<PostPreview
5760
key={node.id}

0 commit comments

Comments
 (0)