Skip to content

Commit 92293f7

Browse files
author
Jacob Parker
committed
.
1 parent 273ad31 commit 92293f7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1640
-1519
lines changed

.babelrc.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
module.exports = {
22
presets: [
33
"@babel/preset-typescript",
4-
["@babel/preset-env", { exclude: ["@babel/plugin-transform-regenerator"] }],
4+
[
5+
"@babel/preset-env",
6+
{
7+
exclude: ["@babel/plugin-transform-regenerator"],
8+
modules: "commonjs",
9+
},
10+
],
511
["@babel/preset-react", { runtime: "automatic" }],
612
],
713
};

README.md

+16-13
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,29 @@
22

33
> [http://jacobdoescode.com](http://jacobdoescode.com)
44
5-
Custom static site builder. Laid out very similar to Jekyll, but uses React heavily under the hood. It has the following advantages:
5+
Custom static site builder. Like Jekyll, but uses React heavily under the hood. It has the following advantages:
66

77
- Assets get tree shaken (for better or worse)
8-
- Optimises assets
8+
- Optimises assets, including automatic conversion of images to WebP
99
- Assets get renamed to their hashes for long-term caching
10-
- Can use React components in Markdown
10+
- MDX-based - sp you can use React components in Markdown
1111
- Generally more React-based - no `{% if blocks %}`
12-
- No more \_includes hacks
12+
- No more `_includes` hacks
1313
- Granular control of how assets get used - including inlining critical CSS/JS if required
1414

15-
It's not perfect, but it's good enough for this site. Mostly, these are the issues that just need time investment to fix.
15+
Using and referencing assets requires React components to do the lifting. The elements `<script>`, `<style>`, `<link>`, `<img>`, and `<video>` equivalent React components in `/core` that do asset optimisation. And finally, `<a href>` works as-is for pages, because page names don't get mangled - but won't work for asset links.
1616

17-
JS and CSS assets don't have "proper" bundlers - there's only ways to transform urls to point to the correct assets - not embed assets within them. CSS uses the url syntax. JS works out-of-the-box for import declarations and expressions, and needs you to use `require.resolve` for urls not part of import statements or expressions. Given enough time, `@import` in CSS and `import` declarations in JS would bundle the resources, and `url` functions and `import` expressions (or `require.resolve` calls) would map to asset references.
17+
The main outstanding issue is JS and CSS assets don't have "proper" bundlers - there's only ways to transform urls to point to the correct assets. It works, but it's less efficient than what things like Webpack will achieve. CSS uses the url syntax to do this. JS works out-of-the-box for `import` declarations and expressions, and needs you to use `require.resolve` for urls not part of import statements or expressions. Given enough time, `@import` in CSS and `import` declarations in JS could/would bundle the resources, and `url()` functions in CSS and `import()` expressions in JS (or `require.resolve` calls) would map to asset references.
1818

19-
Referencing assets requires React components to do the lifting. The elements `<script>`, `<style>`, `<link>`, `<img>`, and `<video>` equivalent React components that do optimisation. And finally, `<a href>` works as-is for pages, because page names don't get mangled - but won't work for asset links.
19+
### Optimisations
2020

21-
This last point less a time investment, and more a conceptual question. CSS works without any special extensions. JS relies a few special extensions. Markdown is half-and-half extensions via React components and some HTML elements working out the box. Where should these asset references be handled?
21+
- Images get compressed in their current format, and a WebP image is generated
22+
- Avif takes too long to generate (for now), so that is skipped
23+
- Use the `<Image>` component from `/core`
24+
- CSS class names and variables are minified to 1 or 2 letters in prod
25+
- You need to use helper functions when referencing classes from JS/MDX
26+
- JS assets get a set of global variables (`CSS_CLASSES` and `CSS_VARS`) that you'll need to use to reference classes
27+
- Assets are renamed to a hash of their content - you'll need to use the `useContent` hook to be able to read and write assets
28+
- This is also important because it's how the watcher works
2229

23-
And just to note,
24-
25-
If you wanted to re-use this work, there's zero config for all the optimisations. Every optimisation I wanted to include is always on. Just fork and remove as necessary.
26-
27-
CSS class and variable names are minified via utility functions in `/core/css`. You need to call these functions in your custom React components. JS assets get a set of global variables (`CSS_CLASSES` and `CSS_VARS`) that you'll need to use.
30+
If you wanted to re-use this work, there's zero options for all the optimisations: every optimisation I wanted to include is always on. Just fork and remove as necessary.

assets/blog.css

+11
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ thead th {
9191
text-align: left;
9292
}
9393

94+
hr {
95+
margin: 32px calc(-1 * var(--gutters));
96+
height: var(--hairline-width);
97+
border: none;
98+
background-color: #f4f4f4;
99+
}
100+
94101
code {
95102
font-size: 0.9em;
96103
font-family: monospace;
@@ -138,6 +145,10 @@ iframe {
138145
}
139146

140147
@media (prefers-color-scheme: dark) {
148+
hr {
149+
background-color: #080808;
150+
}
151+
141152
code {
142153
background-color: #111;
143154
border-color: #222;

assets/cv.css

+9-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
@font-face {
22
src: url(/assets/fonts/CrimsonPro-Roman-VF.woff2);
3-
font-family: Crimson Pro;
3+
font-family: "Crimson Pro";
44
font-style: normal;
55
}
66

77
@font-face {
88
src: url(/assets/fonts/CrimsonPro-Italic-VF.woff2);
9-
font-family: Crimson Pro;
9+
font-family: "Crimson Pro";
1010
font-style: italic;
1111
}
1212

@@ -116,38 +116,31 @@ li p:not(:first-child) {
116116
letter-spacing: 0.2pt;
117117
}
118118

119-
/* {:.class} support is broken - hardcode selectors for now
120-
121-
/* .timelist */
122-
h3 + ul {
119+
.timelist {
123120
padding: 0;
124121
}
125122

126-
/* .timelist */
127-
h3 + ul li::before {
123+
.timelist li::before {
128124
display: none;
129125
}
130126

131-
/* .timelist */
132-
h3 + ul time::before {
127+
.timelist time::before {
133128
content: "(";
134129
}
135130

136-
/* .timelist */
137-
h3 + ul time::after {
131+
.timelist time::after {
138132
content: ")";
139133
}
140134

141135
/* .lead */
142-
h1 + p {
136+
p:first-of-type {
143137
font-size: 16pt;
144138
line-height: 20pt;
145139
font-style: italic;
146140
--font-weight: 350;
147141
}
148142

149-
strong,
150-
.smcp {
143+
strong {
151144
font-style: normal;
152145
text-transform: uppercase;
153146
letter-spacing: 0.4pt;
@@ -176,7 +169,7 @@ strong,
176169
}
177170

178171
/* .lead */
179-
h1 + p {
172+
p:first-of-type {
180173
margin-top: 12pt;
181174
margin-bottom: 12pt;
182175
font-size: 22pt;

components/Icon.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type Props = SVGProps<any> & {
77
};
88

99
export default ({ name, className = "app-logo" }: Props) => {
10-
const svg = useContent().asset(`/assets/icons/${name}.svg`);
10+
const svg = useContent().read(`/assets/icons/${name}.svg`);
1111

1212
return (
1313
<svg

components/Layout.tsx

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import * as React from "react";
2+
import { format } from "date-fns";
3+
import {
4+
ExternalJs,
5+
InlineCss,
6+
InlineJs,
7+
className,
8+
classNames,
9+
cssVariable,
10+
} from "../core/core";
11+
12+
const Header = ({ children }: { children: any }) => (
13+
<div className={className("header")}>{children}</div>
14+
);
15+
16+
const HeaderSection = ({
17+
last,
18+
children,
19+
}: {
20+
last?: boolean;
21+
children: string;
22+
}) => (
23+
<div
24+
className={classNames("header__section", last && "header__section--last")}
25+
>
26+
{children}
27+
</div>
28+
);
29+
30+
const HeaderLogo = () => (
31+
<a href="/" className={classNames("header__logo")}>
32+
Jacob
33+
<br />
34+
does
35+
<br />
36+
code
37+
</a>
38+
);
39+
40+
const HeaderLink = ({ href, children }: { href: string; children: string }) => (
41+
<a href={href} className={classNames("header__link")}>
42+
{children}
43+
</a>
44+
);
45+
46+
const castArray = (x: string[] | string | undefined): string[] => {
47+
if (Array.isArray(x)) {
48+
return x;
49+
} else if (x != null) {
50+
return [x];
51+
} else {
52+
return [];
53+
}
54+
};
55+
56+
type Props = {
57+
date: number | null;
58+
title: string;
59+
description?: string;
60+
css?: string[] | string | undefined;
61+
js?: string;
62+
banner?: JSX.Element;
63+
primary?: string;
64+
children: JSX.Element;
65+
};
66+
67+
export default ({
68+
date,
69+
title,
70+
description,
71+
css,
72+
js,
73+
banner,
74+
primary,
75+
children,
76+
}: Props) => (
77+
<html style={{ [cssVariable("--primary")]: primary }} lang="en">
78+
<head>
79+
<title>{title}</title>
80+
<meta charSet="utf-8" />
81+
<meta name="viewport" content="width=device-width,initial-scale=1" />
82+
{description && <meta name="description" content={description} />}
83+
<InlineJs src="/assets/set-hairline-width.js" />
84+
<InlineCss src={["/assets/base.css", ...castArray(css)]} />
85+
{castArray(js).map((src, index) => (
86+
<ExternalJs key={index} src={src} type="module" defer />
87+
))}
88+
</head>
89+
<body>
90+
{banner}
91+
<Header>
92+
<HeaderLogo />
93+
<HeaderSection>Apps</HeaderSection>
94+
<HeaderLink href="/pocket-jam">Pocket Jam</HeaderLink>
95+
<HeaderLink href="/piano-tabs">Piano Tabs</HeaderLink>
96+
<HeaderLink href="/technicalc">TechniCalc</HeaderLink>
97+
<HeaderLink href="/freebies">Freebies</HeaderLink>
98+
<HeaderSection last>Developement</HeaderSection>
99+
<HeaderLink href="/blog">Blog</HeaderLink>
100+
<HeaderLink href="https://github.com/jacobp100">Github</HeaderLink>
101+
</Header>
102+
{children}
103+
{date != null && (
104+
<span className={className("published-on")}>
105+
Published on{" "}
106+
<time dateTime={new Date(date).toISOString()}>
107+
{format(new Date(date), "do MMMM yyyy")}
108+
</time>
109+
</span>
110+
)}
111+
</body>
112+
</html>
113+
);

components/Posts.tsx

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import * as React from "react";
2-
import { getPosts } from "../core/core";
2+
import { usePages, usePagesData } from "../core/core";
33

4-
export default () => (
5-
<ul>
6-
{getPosts().map(({ url, title, filename }) => (
7-
<li key={filename}>
8-
<a href={url}>{title ?? url}</a>
9-
</li>
10-
))}
11-
</ul>
12-
);
4+
export default () => {
5+
const allPages = usePages();
6+
const posts = allPages.filter((page) => page.filename.startsWith("/posts"));
7+
const postsData = usePagesData(posts);
8+
9+
return (
10+
<ul>
11+
{postsData.map(({ url, title }) => (
12+
<li key={url}>
13+
<a href={url}>{title}</a>
14+
</li>
15+
))}
16+
</ul>
17+
);
18+
};

core/api/api.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
export { default as dev } from "../util/dev";
2-
export type { File } from "../util/projectFiles";
3-
export { getPages, getPosts } from "../util/projectFiles";
42

53
export * from "./components/components";
64
export { assetTransform } from "./assetTransformer";
75
export { cssVariable, classNameForOrigin, className, classNames } from "./css";
6+
export type { Content } from "./useContent";
87
export {
98
default as useContent,
10-
componentPath,
11-
getComponentNames,
12-
layoutPath,
13-
assetPath,
149
createContentContext,
1510
ContentContext,
1611
} from "./useContent";
12+
export { usePages, usePagesData } from "./usePages";

core/api/assetTransforms/transformAsset.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@ export default async (content: Content, asset: string) => {
99

1010
switch (extension) {
1111
case ".js": {
12-
const js = await transformJs(content, content.asset(asset), {
12+
const js = await transformJs(content, content.read(asset), {
1313
module: true,
1414
});
1515
return content.write(js, { extension: ".js" });
1616
}
1717
case ".min.js": {
18-
const js = content.asset(asset);
18+
const js = content.read(asset);
1919
return content.write(js, { extension: ".js" });
2020
}
2121
case ".css": {
22-
const css = await transformCss(content, content.asset(asset));
22+
const css = await transformCss(content, content.read(asset));
2323
return content.write(css, { extension: ".css" });
2424
}
2525
case ".html": {
26-
const html = await transformHtml(content, content.asset(asset));
26+
const html = await transformHtml(content, content.read(asset));
2727
return content.write(html, { extension: ".html" });
2828
}
2929
default:
30-
return content.write(content.assetBuffer(asset), { extension });
30+
return content.write(content.readBuffer(asset), { extension });
3131
}
3232
};

core/api/assetTransforms/transformCss.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import dev from "../../util/dev";
1010
import transformAsset from "./transformAsset";
1111

1212
const transformUrls = (content: Content) => {
13-
const urlRegExp = /url\(['"]?(\/assets\/[^'")]+)['"]?\)/g;
13+
const urlRegExp = /url\(['"]?(\/[^'")]+)['"]?\)/g;
1414

1515
return (root: Root) =>
1616
nestedAsync((asyncTransform) => {
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
11
import * as React from "react";
22
import hljs from "highlight.js";
3-
import { classNames } from "../../css";
4-
import htmlTags from "./_htmlTags";
3+
import { classNames } from "../css";
54

6-
const htmlComponents = htmlTags.reduce((accum, TagName) => {
7-
accum[TagName] = (props: any) => (
8-
<TagName {...props} className={classNames(props.className)} />
9-
);
10-
11-
return accum;
12-
}, {} as Record<string, any>);
13-
14-
htmlComponents.code = (props: any) => {
5+
export default (props: any) => {
156
if (props.className != null) {
167
const language = props.className.match(/language-([^\s]+)/)[1];
178
let __html = hljs.highlight(props.children, { language }).value;
@@ -23,5 +14,3 @@ htmlComponents.code = (props: any) => {
2314
return <code>{props.children}</code>;
2415
}
2516
};
26-
27-
export default htmlComponents;

core/api/components/ExternalCss.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { transformCss } from "../assetTransforms/assetTransforms";
55

66
const transform = assetTransform<string, [string]>(
77
async (content, src) => {
8-
const input = content.asset(src);
8+
const input = content.read(src);
99
const output = await transformCss(content, input);
1010
return content.write(output, { extension: ".css" });
1111
},

0 commit comments

Comments
 (0)