Update posts, add glob depdency.

This commit is contained in:
Dillon Kearns 2019-07-23 06:08:00 -07:00
parent 1b8b4c0bd3
commit 26e9e755f2
6 changed files with 231 additions and 26 deletions

View File

@ -1,4 +0,0 @@
Here are some articles. You can learn more at.....
|> IndexContent
posts = articles

View File

@ -0,0 +1,158 @@
|> Article
author = Dillon Kearns
title = Moving Faster with Tiny Steps in Elm
tags = software other
description =
How I learned to use elm-markup.
|> Image
src = mountains.jpg
description = The Elm Architecture
In this post, were going to be looking up an Article in an Elm Dict, using the tiniest steps possible.
Why use tiny steps? Simple! Because we want to write Elm code faster, and with more precise error messages to guide us through each step.
|> H2
Setting Up Your Environment
The point of taking tiny steps is that you get constant, clear feedback. So before I walk through the steps, here are some things to set up in your editor to help you get more feedback:
|> List
- See Elm compiler errors instantly without manually running a command. For example, have elm-make run whenever your files change. Or run elm-live, webpack, or parcel in watch mode.
- Even better, get error messages in your editor whenever you save. Here are some instructions for configuring Atom with in-editor compiler errors.
- Note that with both of these workflows, I recommend saving constantly so you get instant error messages.
- Atom also gives you auto-completion, which is another helpful form of feedback. Elm-IntelliJ is another good option for this.
|> H2
The Problem
Were doing a simple blog page that looks up articles based on the URL. Weve already got the wiring to get the article name from the URL (for example, localhost:8000\\/article\\/{Code|articlePath}). Now we just need to take that {Code|articlePath} and use it to look up the title and body of our article in a Dict.
|> H2
The Tiny Steps
If youd like to see a short video of each of these steps, or download the code so you can try them for yourself, just sign up here and Ill send you a link.
Okay, now lets walk through our tiny steps for building our Dict!
|> H2
Step 0
Always respond with “Article Not Found.”
We start with the failure case because its easiest. This is sort of like returning 1 for for factorial(1). Its just an easy way to get something working and compiling. Think of this as our “base case”.
|> Code
view : Maybe String -> Browser.Document msg
view maybeArticlePath =
articleNotFoundDocument
articleNotFoundDocument : Browser.Document msg
articleNotFoundDocument =
{ title = "Article Not Found"
, body = [ text "Article not found..." ]
}
Weve wired up our code so that when the user visits mysite.com/article/hello, youll see our “Article Not Found” page.
|> H2
Step 1
Hard code an empty dictionary.
|> Code
view : Maybe String -> Browser.Document msg
view maybeArticlePath =
Dict.get articlePath Dict.empty
|> Maybe.map articleDocument
|> Maybe.withDefault articleNotFoundDocument
Why bother? We know this lookup will always give back Nothing! So we havent changed the behavior at all.
This step is actually quite powerful. Weve wired up our entire pipeline to reliably do a dictionary lookup and get back Nothing every time! Why is this so useful? Well, look at what we accomplish with this easy step:
Weve made the necessary imports
We know that all the puzzle pieces fit perfectly together!
So even though weve done almost nothing, the work that remains is all teed up for us! This is the power of incremental steps. Weve stripped out all the risk because we know that the whole picture ties together correctly.
When you mix in the hard part (building the actual business logic) with the “easy part” (the wiring), you end up with something super hard! But when you do the wiring first, you can completely focus on the hard part once thats done. And amazingly, this small change in our approach makes it a lot easier.
|> H2
Step 2
Extract the dictionary to a top-level value.
|> Code
view : Maybe String -> Browser.Document msg
view maybeArticlePath =
Dict.get articlePath articles
|> Maybe.map articleDocument
|> Maybe.withDefault articleNotFoundDocument
articles =
Dict.empty
This is just a simple refactor. We can refactor at any step. This is more than a superficial change, though. Pulling out this top-level value allows us to continue tweaking this small area inside a sort of sandbox. This will be much easier with a type-annotation, though…
|> H2
Step 3
Annotate our articles top-level value.
|> Code
view : Maybe String -> Browser.Document msg
view maybeArticlePath =
Dict.get articlePath articles
|> Maybe.map articleDocument
|> Maybe.withDefault articleNotFoundDocument
articles : Dict String Article
articles =
Dict.empty
This step is important because it allows the Elm compiler to give us more precise and helpful error messages. It can now pinpoint exactly where things go wrong if we take a misstep. Importantly, were locking in the type annotation at a time when everything compiles already so we know things line up. If we added the type annotation when things werent fully compiling, we wouldnt be very confident that we got it right.
|> H2
Step 4
Use a "synonym" for Dict.empty.
|> Code
articles : Dict String Article
articles =
Dict.fromList []
Whats a synonym? Well, its just a different way to say the exact same thing.
Kent Beck calls this process “Make the change easy, then make the easy change.” Again, this is all about teeing ourselves up to make the next step trivial.
|> H2
Step 5
Add a single item to your dictionary
|> Code
Dict.fromList
[ ( "hello"
, { title = "Hello!", body = "Here's a nice article for you! 🎉" }
)
]
Now that weve done all those other steps, this was super easy! We know exactly what this data structure needs to look like in order to get the type of data we need, because weve already wired it up! And when we finally wire it up, everything just flows through uneventfully. Perhaps its a bit anti-climactic, but hey, its effective!
But isnt this just a toy example to illustrate a technique. While this technique is very powerful when it comes to more sophisticated problems, trust me when I say this is how I write code all the time, even when its as trivial as creating a dictionary. And I promise you, having this technique in your tool belt will make it easier to write Elm code!
|> H2
Step 6
In this example, we were dealing with hardcoded data. But its easy to imagine grabbing this list from a database or an API. Ill leave this as an exercise for the reader, but lets explore the benefits.
When you start with small steps, removing hard-coding step by step, it lets you think up front about the ideal data structure. This ideal data structure dictates your code, and then from there you figure out how to massage the data from your API into the right data structure. Its easy to do things the other way around and let our JSON structures dictate how were storing the data on the client.
|> H2
Thanks for Reading!
You can sign up here for more tips on writing Elm code incrementally. When you sign up, Ill send the 3-minute walk-through video of each of these steps, and the download link for the starting-point code and the solution.
Let me know how this technique goes! Ive gotten a lot of great feedback from my clients about this approach, and I love hearing success stories. Hit reply and let me know how it goes! Id love to hear how youre able to apply this in your day-to-day work!

View File

@ -1,5 +1,9 @@
const { Elm } = require("./Main.elm");
const { version } = require("../../package.json");
import * as glob from "glob";
console.log("glob", glob.sync("_posts/**/*.emu", {}));
console.log("glob", glob.sync("_pages/**/*.emu", {}));
let app = Elm.Main.init({
flags: { argv: process.argv, versionMessage: version }

70
package-lock.json generated
View File

@ -1277,6 +1277,29 @@
"physical-cpu-count": "^2.0.0"
}
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
"dev": true
},
"@types/glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
"integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
"dev": true,
"requires": {
"@types/events": "*",
"@types/minimatch": "*",
"@types/node": "*"
}
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"@types/node": {
"version": "12.6.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz",
@ -1589,8 +1612,7 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"base": {
"version": "0.11.2",
@ -1719,7 +1741,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2731,8 +2752,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"concat-stream": {
"version": "1.6.2",
@ -3514,6 +3534,22 @@
"temp": "0.8.3",
"which": "1.3.1",
"xmlbuilder": "^8.2.2"
},
"dependencies": {
"glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
"integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.2",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
}
},
"elmi-to-json": {
@ -3964,8 +4000,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "1.2.4",
@ -4524,15 +4559,14 @@
}
},
"glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
"integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
"dev": true,
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
@ -4875,7 +4909,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@ -4884,8 +4917,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.5",
@ -5592,7 +5624,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -5953,7 +5984,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@ -6209,8 +6239,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-key": {
"version": "2.0.1",
@ -8505,8 +8534,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": {
"version": "5.2.2",

View File

@ -11,8 +11,11 @@
},
"author": "Dillon Kearns",
"license": "BSD-3",
"dependencies": {},
"dependencies": {
"glob": "^7.1.4"
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/node": "^12.6.8",
"elm": "^0.19.0-no-deps",
"elm-hot": "^1.0.1",

View File

@ -38,6 +38,7 @@ document indexView =
, body =
Mark.manyOf
[ header
, h2
, image
, list
, code
@ -207,6 +208,21 @@ header =
Element.paragraph
[ Font.size 24
, Font.semiBold
, Font.center
, Font.family [ Font.typeface "Raleway" ]
]
children
)
text
h2 : Mark.Block (Element msg)
h2 =
Mark.block "H2"
(\children ->
Element.paragraph
[ Font.size 18
, Font.semiBold
, Font.alignLeft
, Font.family [ Font.typeface "Raleway" ]
]