my blog https://overreacted.io

wip

Changed files
+236
public
untitled
+201
public/untitled/index.md
··· 1 + --- 2 + title: Composable HTML 3 + date: '2030-12-25' 4 + spoiler: Tags on both sides. 5 + --- 6 + 7 + Here's a piece of HTML: 8 + 9 + ```js 10 + <html> 11 + <body> 12 + <p>Hello, world</p> 13 + </body> 14 + </html> 15 + ``` 16 + 17 + Suppose it was the only piece of HTML you've ever seen in your life. If you had complete freedom, which features would you add to HTML, and in what order? 18 + 19 + How would you evolve the *HTML itself?* 20 + 21 + --- 22 + 23 + ### Custom Tags 24 + 25 + Personally, I'd like to start by adding a way to define our own custom HTML tags. 26 + 27 + It doesn't need to be complicated. We can just use JavaScript functions: 28 + 29 + ```js {3,7-9} 30 + <html> 31 + <body> 32 + <Greeting /> 33 + </body> 34 + </html> 35 + 36 + function Greeting() { 37 + return <p>Hello, world</p> 38 + } 39 + ``` 40 + 41 + We'll *specify* that sending HTML involves unwrapping all of these custom tags: 42 + 43 + ```js {3} 44 + <html> 45 + <body> 46 + <p>Hello, world</p> 47 + </body> 48 + </html> 49 + ``` 50 + 51 + When there are no functions left to call, the HTML is ready to be sent. 52 + 53 + Neat feature, huh? 54 + 55 + Good thing we got it in early. 56 + 57 + It might influence how we approach everything else. 58 + 59 + --- 60 + 61 + ### Props 62 + 63 + Functions take parameters. 64 + 65 + Let's support passing and receiving them: 66 + 67 + ```js {3-4,9} 68 + <html> 69 + <body> 70 + <Greeting name="Alice" /> 71 + <Greeting name="Bob" /> 72 + </body> 73 + </html> 74 + 75 + function Greeting({ name }) { 76 + return <p>Hello, {name}</p> 77 + } 78 + ``` 79 + 80 + Of course, there's no reason why those parameters have to be *strings*. 81 + 82 + We might want to pass an object to `Greeting`: 83 + 84 + ```js {3-4,10-12} 85 + <html> 86 + <body> 87 + <Greeting person={{ name: 'Alice', favoriteColor: 'purple' }} /> 88 + <Greeting person={{ name: 'Bob', favoriteColor: 'pink' }} /> 89 + </body> 90 + </html> 91 + 92 + function Greeting({ person }) { 93 + return ( 94 + <p style={{ color: person.favoriteColor }}> 95 + Hello, {person.name} 96 + </p> 97 + ); 98 + } 99 + ``` 100 + 101 + Objects let us group related stuff together. 102 + 103 + Suppose we wanted to send this HTML. First, according to what we specified earlier, we'd have to replace all custom tags like `Greeting` with their output: 104 + 105 + ```js 106 + <html> 107 + <body> 108 + <p style={{ color: 'purple' }}>Hello, Alice</p> 109 + <p style={{ color: 'pink' }}>Hello, Bob</p> 110 + </body> 111 + </html> 112 + ``` 113 + 114 + But what should we do with the `style={{ color: '...' }}` objects? If we wanted to translate our "imaginary" HTML into "real" HTML, we'd stringify them: 115 + 116 + ```js {3,4} 117 + <html> 118 + <body> 119 + <p style="color: purple">Hello, Alice</p> 120 + <p style="color: pink">Hello, Bob</p> 121 + </body> 122 + </html> 123 + ``` 124 + 125 + But we don't *have to* turn our "imaginary" HTML into "real" HTML right away. We can stay in the imaginary land for a bit longer by turning *the entire thing* into JSON. Note how in this case, `style` can remain completely intact as an object within it: 126 + 127 + ```js {6,10} 128 + ["html", { 129 + children: ["body", { 130 + children: [ 131 + ["p", { 132 + children: "Hello, Alice", 133 + style: { color: "purple" } 134 + }], 135 + ["p", { 136 + children: "Hello, Bob", 137 + style: { color: "pink" } 138 + }] 139 + ] 140 + }] 141 + }] 142 + ``` 143 + 144 + We can easily turn this JSON into the "real" HTML later if we want. But it's a *strictly richer* representation because it preserves objects in a way that's easy to parse. 145 + 146 + Going forward, we'll co-evolve these two representations. 147 + 148 + --- 149 + 150 + ### Async Tags 151 + 152 + We're previously hardcoded some objects into our code: 153 + 154 + ```js {3-4} 155 + <html> 156 + <body> 157 + <Greeting person={{ name: 'Alice', favoriteColor: 'purple' }} /> 158 + <Greeting person={{ name: 'Bob', favoriteColor: 'pink' }} /> 159 + </body> 160 + </html> 161 + 162 + function Greeting({ person }) { 163 + // ... 164 + } 165 + ``` 166 + 167 + Now let's read this stuff from the disk. 168 + 169 + ```js {3-4} 170 + <html> 171 + <body> 172 + <Greeting person={JSON.parse(await readFile('./alice123.json', 'utf8'))} /> 173 + <Greeting person={JSON.parse(await readFile('./bob456.json', 'utf8'))} /> 174 + </body> 175 + </html> 176 + 177 + function Greeting({ person }) { 178 + // ... 179 + } 180 + ``` 181 + 182 + Actually, this looks a bit repetitive--let's have `Greeting` itself read from the disk. 183 + 184 + ```js {3-4,8-10} 185 + <html> 186 + <body> 187 + <Greeting username="alice123" /> 188 + <Greeting username="bob456" /> 189 + </body> 190 + </html> 191 + 192 + async function Greeting({ username }) { 193 + const filename = `./${username.replace(/\W/g, '')}.json`; 194 + const person = JSON.parse(await readFile(filename, 'utf8')); 195 + return ( 196 + <p style={{ color: person.favoriteColor }}> 197 + Hello, {person.name} 198 + </p> 199 + ); 200 + } 201 + ```
+35
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2017", 4 + "lib": [ 5 + "dom", 6 + "dom.iterable", 7 + "esnext" 8 + ], 9 + "allowJs": true, 10 + "skipLibCheck": true, 11 + "strict": false, 12 + "noEmit": true, 13 + "incremental": true, 14 + "module": "esnext", 15 + "esModuleInterop": true, 16 + "moduleResolution": "node", 17 + "resolveJsonModule": true, 18 + "isolatedModules": true, 19 + "jsx": "preserve", 20 + "plugins": [ 21 + { 22 + "name": "next" 23 + } 24 + ] 25 + }, 26 + "include": [ 27 + "next-env.d.ts", 28 + ".next/types/**/*.ts", 29 + "**/*.ts", 30 + "**/*.tsx" 31 + ], 32 + "exclude": [ 33 + "node_modules" 34 + ] 35 + }