Rethinking JavaScript with Generator Functions

11_Patrick Smith-1: Hi, welcome to my talk, JavaScript Regenerated, where I'll be using generator functions to design components.

Let's start off where I started off, writing scripts.

So here's a very simple script.

We ask the user for some sort of input that they will type in.

We take that text and we transform it into uppercase, and then we show that text back to the user.

And one of the things we noticed with this here is it's run in order from one, two to three.

And if this script had 10 lines it'd run from line one to line 10.

In today's world, where I write components, we actually have a similar model where here we've got some prompts, so some sort of input of some texts, we transform it into uppercase, and then we show the user by putting it in a heading.

And so this has also run from line one to line three.

If our, component was a hundred lines or 10 lines, it would run in order.

This model is really, really fantastic because it's so simple.

And so if there's some sort of error that occurs halfway through rendering my component or running my script, I can probably scroll to around halfway in my file to find out where that issue happened.

So after my start in scripting, I went and just wanted to build a real Mac Apps.

And so I reached for Objective-C and learned about the object oriented world of designing classes and writing objects.

So object oriented code is a richer model, but it also leads to a decision explosion.

So you have this way of doing something or we have that way.

So just quickly, what this means.

So here is a class that represents a recipe and it has some ingredients and some steps.

And so already, just to have those two things we have to decide, do we put these, do we pass these things along to the recipe in this constructor, when we initialize the recipe or do we allow the recipe to be initialized first, and then we allow mutating or setting those properties?

Maybe we allow both because we want to have a flexible API.

Another example is when something has to talk to another object, how should those two objects interact?

So here we have a example of a chef is going to make a recipe that's passed along to a method, makeRecipe.

So one of the recipes might call for using the oven.

So the chef might need to have access to an oven and then need to switch that oven on.

So you can see this here, this.oven.turnOn.

So that chef literally has some sort of access to some oven.

But as you can probably guess not all recipes need ovens.

So perhaps that responsibility of switching the oven on belongs instead to the recipe where the recipe can ask itself, "Hey, I think I know that the oven needs to be preheated, so I'll switch it on right at the start of making this recipe".

And then the last example I've got the need to purchase some ingredients.

Where does this thing live?

And so, you know, this is domain modeling.

This is something that you could, should still consider in a functional programming mode or whatever other paradigm you use, but just, I'm just highlighting here just to do, send just to do one task, we have to find the sort of home for this thing.

So one of the things that my work I try to focus on is the what, and not the how and components have been brilliant for this.

So the how is a step-by-step process of every single thing needed to get a particular problem solved.

What is more a zoom out and just looking at the key parts that my problem has, and the what is more of a zooming out and just looking at the key parts of my solution.

And so I like this approach because it gets closer to the why, which is what my user's problem are.

There's this trope, there's this common idea that the two hardest things in programming it's naming and cache invalidation.

And one of the benefits of components is that they force you to tackle both.

So they force you to think of good names of things, and they forced you to wrangle with, when do I update this particular state or data.

So really components really let you focus in on that, that what, and so they focus on that meaning.

And one of the things we often talk about in web design is semantic semantic CSS, semantic HTML.

So I believe that semantic HTML is really important.

I think ARIA roles are vital.

We should be thinking about them when we design our web apps.

I think semantic CSS is less important.

And because it's mostly for the authors, if you rename a CSS class, a user doesn't see the difference.

But in, from an authorship point of view, components are our new semantic layer.

So there's two semantic layers, HTML, and our components.

Components.

range from primary button all the way up to an app and everything in between.

So navigation and modals and so forth.

Some of the benefits of components are that they are easy to change, easy to delete, easy to divide up, easier to debug, you could argue, and they also represent meaningful concepts.

So that name and the name of their props, that, that core meaning that we're unveiling in our app.

And hopefully they it's something meaningful to a user as well.

So if they saw.

If they stumbled across our design system and they saw a random the component, they might actually understand what that meant the component model.

So one of the examples of a component model is the, like the react engine that renders components, or also one I've used is the Swift UI.

This is the contract that it gives you.

So you define your components and then you pass along to some system and it runs and keeps it updated So if you've used something like react you render an element, and then you pass that along to, to the render method of react and it keeps that UI up-to-date.

And so here's the contract that it uses.

Initially it runs your component from top to bottom.

And then when they use the taps and interacts with your app, it runs it again from top to bottom, when an API request succeeds, it runs it from top to bottom.

And if it fails again, top to bottom, and when any state changes at all, it's always going to do this process of running it from the very top of your component to the bottom.

The benefit of this is that we no long have to think about how to optimize this or what this case means.

And when, if this fails with running different codes or when this succeeds.

the contract is just very straightforward.

It's conceptually, conceptually it's much easier to model in our heads.

It's it's very straightforward One approach that I found with objective C was this more of a focus on a message rather than a method.

And so in JavaScript classes, we have more of a focused on the method.

So here's an example of a counter and I can add whatever numbers I want to it So here I'm passing in an amount, and you could probably imagine some sort of property being incremented here.

So here I create this new counter And then I say, Hey, add one to, yourself and in objective C we can do something similar.

So we've got a counter and, add one to it.

But one of the things here, which you can see there's a bit of ceremony ran, is that I can actually extract out this, need to.

add One, and I can make an object that this represents the sending of that message.

So that the method that receives the call to add one and the actual message itself which just simply just says, add one in isolation are two separate things.

And so here we build up this capturing of this message.

You can see we're passing in the amount here and we're going to call it on counter, and then we can invoke this so we can send the message twice.

And so, because of these two things are separate.

We can actually think about the message as a core concept and the sender and receiver as being independent.

So we're reducing coupling and that takes us to message generators.

So message generators are an approach of using generator functions, but sending and receiving messages.

And so to send the message, I'll yield the particular value?

So here we've just got some string, but it could be, anything.

And then if the receiver of that message wants to reply to me, I can get the reply back from when I yield And then if my component sort of came up with some sort of final result, I can just return that we've got these three core concepts here, sending messages, receiving messages and returning.

a result And so in actual JavaScript syntax, he you can see a demonstration of sending a string, sending numbers, sending a regular expression, sending, the new.

ECMAScript symbols.

We can also send an array.

We can really send anything.

We can even send other generator functions.

So this is where we get into the model of, react with its component.

tree So we could have one generator that then uses another generator to do its work, and it could continue, as many layers as it wanted.

Here's an illustration of receiving our reply after sending the message ABC, and then here you can see we return.

a final result from.

our Function, including the reply, if we wish.

So this is just a zoomed out view, but I'll go into more concrete, demonstrations.

So that generator function, I think of it as a message sender Here on the other side is a message receiver, which I call message processes.

And so the basic algorithm, the basic process that they do is as follows.

They accept a generator function.

They have some sort of state that they're going to initialize They iterate through the generator.

So they call the generator function, get a generator and then iterate throug it I check if that message is recognizable.

So we saw a message before of ABC as a string.

So you could check if it ABC meant something to it, unrecognized messages are ignored.

So it skipped to the next message.

And so, these message processes can receive messages that they just ignore, but it doesn't blow up.

So it's very safe recognize messages, the process.

So if it was a regular expression, would match against its, input or its state The state might change due to processing the message.

So we might append a string or add some numbers.

So in the example of a counter we might be incrementing, some sort of variable.

We reply to messages that expect one.

So the processor has the responsibility of actually making and sending that reply.

So an example of that is if a regular expression was matched, we could return the actual matches And it keeps iterating through the generator.

So all the messages until it's done and they return some sort of final result, which is probably, dependent on the messages that are received and whatever state that initialized.

Here's an example of a parser library that I wrote.

the actual source code is not that long.

So here we've got an example of passing an IP address so IP addresses are strings that look like this four numbers separated by full stops And so this processor that is called parse will be receiving these messages that are yielding So here we're yielding a regular expression that will match, any number of digits and that returns a string.

So we received a reply that string, we then want to turn into an actual integer.

So that's what we get here firstDigit Because the full stop, there's nothing really to it.

We just want to skip over it.

I'm just yielding a full stop as a string, and then we continue.

So you can see it's basically the same structure again and again.

So we get each digit turn it into an actual number.

Here I yield a message saying that this, we must be, at the end of parsing So we must be at the end of the string.

And then my IP address, parsing component is going to return the four digits the actual four numbers that are.

found And so by running parse with my IP address parsing component and this input, we get this as a result, including the four digits.

And so you saw a bit of repetition here, so we could, find And we could use the same patterns that we use in react and other component libraries to pull out the common, reasonable pieces.

So I've pulled out the concept of a digit.

And so here, we're going to, again, yield this regular expression to get this.

string And then I'm going to parse it into an actual number, and then I can add some more steps here.

So I'm going to validate that the number is actually in the range I expected, so it must be equal or greater to zero and less than or equal to 255.

So if it's outside that range, we're going to return.

an error And then we return an actual number.

So this is when it's valid.

And so you can see here, we can just yield a reference to this generator function here, and it will, run this code again.

And again, every time we yield and we'll get this, number value back.

So we've, really focused in this IP address component to just, declare what exactly its intent is So you can see that the result is the same as before.

Here's another example, closer to react.

So here's a simple example of rendering some HTML.

So we're using this little helper function to render some HTML.

So you might've imagined that that helper might escape the HTML or, offer some text highlighting or whatever.

And so its output looks, something like this And he's a preview.

It's very simple.

Here the other side where we actually process the, this HTML.

So we get past our generator.

So this is the generator function here.

This HTML component, we get passed a reference, we're going to call it and then we're going to iterate through it.

And each iteration is going to give us a message.

So each message is going to be one of these.

And so this HTML will help, return an object.

With a type and a raw property.

So we check if we recognize this particular message, Hey, if the type is HTML, if it is then we're going to output our selves the raw HTML, I'm going to stick a new line in between.

And so here we call this processor, we get a generator back and we can turn that generator into.

an array And then that array of strings, we're going to join all together into one HTML string.

The reason why it returns a generator is because we can do more than just one single string.

We could string these values out so we could use the new readable stream, which is part of the, one web specs.

And we can iterate through it here and we can push each HTML snippet out.

This is really powerful because these yields don't have to be synchronous.

There can be space in between each line.

And so we could yield a promise at this point, side-effect some data and then our processor would wait until that promise has a result before going onto the next line here.

It's really, really powerful back to our parser Here's another example with AWS region strings Here's an example where we could yield a choice of messages.

And so it's going to check this one and then check this one.

If it didn't match and check it until it finds anything.

And again, here with the, compass direction.

So we return those three things that it found.

And so here we passed in the U S west one and it turned it into an object, primary us secondary west, and the digit it found at the end was one.

And so we can do some interesting stuff with, Error handling.

So here we passed in something that's not a valid, AWS region.

And so it can tell us that it failed on this particular message.

So basically expected one of these values to be passed, but it got snagged at the XX.

here So we can do some really powerful stuff, with feedback and, the code for this is very, very simple.

Here's another example with state machines.

And so we can actually have a generator function for each state that we'll have and send messages to, what transition should be made on depending on what events happened.

So here's a general machine to fetch a particular URL so that URL will be passed in here.

And here are our four states.

Idle Loading success and failure.

And we're going to return idol because it's going to start off, just idling.

It hasn't done any work to fetch the data yet.

So when this receives a fetch event, it's going to start loading and then it will, call this generator function here and process it's messages.

When we load on entry into the state, we're going to call this function here and we're going to return the promise of fetching this URL If that succeeds, we're going to go to the success state.

If it fails, we're going to go to the failure state and the failure state, allows us to retry if we wish.

And so I can compose these like components and I can pass a specific URL to my API here.

And so with my example, API loader I'm going to start it.

It's going to start off in the idle state.

We're going to tell it to start fetching and it's gonna be in the loading zone.

And that, state, returned a promise.

So it can, await that promise and get its results and see the fetch data result here.

And when that has finished loading, there'll be in the success state.

So my final example here is a component that speaks many languages.

And so it's, going to have some HTML.

It's going to have some high level concept of links.

It's going to load some remote images, and it's also going to define some CSS and this could go further.

I've kept this quite, paired back, but we could have, Brett client-side JavaScript being loaded here.

we could have the state machine that we saw earlier.

We can really compose these things together and send different flavors of messages.

So the output of this looks, something like this, some of these are live links.

This text here is defined by the CSS above here with a survey class.

And so we've got these different processes that, interpret these messages in different ways.

So here's our HTML.

It looks much like that.

HTML processor from before.

So the HTML looks like this, so it understood the link messages and turned them into a href tags and understood the remote images and turn them into image.

tags And you can see it produced this much HTML Here's the CSS, result.

And then here's these other, processes.

So here it's going to get all the links that were sent.

So back here we had these three links and we turned that into an array and I display this.

here And so, because we've got this as this data, we can start to process this and interpret in different ways.

So here I've got a set of known links and I want to check each link that was rendered and see if it matches my set of known links.

If it doesn't, if it doesn't recognize this link, it's going to add it to a new set of invalid links.

And so we can see this output here.

So the terms link.

Wasn't in the set here.

And so it's saying that it's invalid.

So this is, like some sort of, and so this is able to add extra validation and processing and understanding of our components.

They're not just speaking HTML we're at using higher level concepts.

Another example that you might want to do sometimes is, some sort of prefetching, here I'm going to be doing DNS prefetching.

So if the browser knows about the origin that you're going to be learning an image from, in this example, we had an image link at Unsplash and an image link.

at imgur dot com.

And so because these remote images were special messages that were sent, I can inspect the image, URL and parse out just the origin.

So the origin's this part here Basically the domain and the HTTPS.

And then with those, these origins here, I can then iterate through them and spit out some HTML for my meta-tags.

So you stick these tags in your head and then the browser will, see these before it actually gets down to the image tags further down in your body.

And so that will look like this.

So we had One component that was interpreted in, four different ways.

React populized the, model of your view being a function on some sort of data.

And so as the data changed, the view was automatically updated by React All you did was pass the function and that function just declared that, that structure I'm going to ask why don't we have this model for many more things.

So for instance, why don't we have a function on the URL to get the route Why don't we have a function on the route to get the API data?

Why don't we have a function on some sort of form that the user is, entering data into to turn that into a request to the API.

Why don't we have some sort of untrusted input that we can sanitize and you can see there's many more examples, and I'm sure you can come up with your own cases.

This model is really fantastic, and I believe we should be using it.

more JavaScript is everywhere.

It's in our pocket, it's in our laptops, it's in the cloud.

And so it runs in all these different environments, generators have amazing support.

They work in modern browsers, modern cloud services that work with TypeScript and JSDock They work in modern IDEs like visual studio code.

They work with code formatters and linters The only place that they.

Might not work as well is in internet Explorer, but you can get away with using Babel And so they work in all of these environments without needing any compilers or any tooling.

They just work.

JavaScript is everywhere.

I'm running it today.

And I believe I will still be running it in 10 years.

It works with the phone in our pockets, works with the laptop at our desk.

It works in the cloud, including in low latency, edge environments, like CloudFlare and Fastly generators have amazing support.

With modern browsers, modern cloud services.

They work in TypeScript and JSDoc modern IDEs like VSCode So you get autocomplete and syntax checking.

They work with code formatters and linters The one place that you don't work as well is in internet Explorer, but they do get transformed by Babel.

So you may see some success There, They're a modern JavaScript feature that we can start using today and they work without any need for compilers or any other tooling.

They just work in vanilla JavaScript.

And in this new world that enhances it.

Generator functions are just a modern JavaScript feature that works in vanilla Java.

And they also are fantastically supported in this wide ecosystem of tooling.

Here's some of the open source libraries that I've shown in this presentation.

We've got a parser we've got an HTML renderer we've got state machines.

We've also got a pattern matching library that I didn't show, but feel free to look at these on GitHub, give me your feedback.

And, I'm sure there's many more ideas that we can do here.

There's a lot more to learn here.

I recommend that you check out Lydia, Hallies talk on generator functions, as she'll go more into the detail of how they work.

I write about these topics and more.

I have two websites, one regenerated.dev, which focusses specifically on patterns around using generator functions in this component style.

And components.guide, zooms out more looking at accessibility, react modern web standards, and also, aims for an audience of, web developers, but also product managers and designers.

Thanks for watching this presentation.

I use the remix react framework to do my slides, used hero icons for my icons.

I, got a lot from this, blog post by Dan here, preparing for a tech talk.

Please reach out to me.

I'm concreteniche on Twitter and RoyalIcing on github.

If you have any questions at all, olease just reach out, and if you make anything interesting, just please send it my way.

I'm really excited about this topic.

So I think there's going to be a lot here in the next few years.

Thank you.

JavaScript Regenerated

Make Components using
Message Generators

  • Linear thinking
  • Object-oriented
  • What, not how
  • Two hardest things
  • Methods vs messages
  • Message generators
  • Message processors
  • Sending functions

Make Components using
Message Generators

  • Linear thinking
  • Object-oriented
  • What, not how
  • Two hardest things
  • Methods vs messages
  • Message generators
  • Message processors
  • Sending functions
  • HTML renderer
  • Schema validator
  • Parser
  • State machine
  • Multicolored components
  • More than the view
  • JavaScript’s benefits
  • Open source libraries
  • Thanks

Reads and runs from top to bottom

Scripts are Linear

  1. text = user.askInput()
  2. text = text.uppercase</li>
  3. user.show(text)

The script is run in order: 1, 2, 3.

Components are Linear

  1. let text = props.text
  2. text = text.uppercase
  3. return <h1>{text}</h1>

The component is run in order: 1, 2, 3.

Object-Oriented Modelling

  • A richer model
  • Decision explosion
  • Do we do it this way or that way?
class Recipe {
	// Has ingredients & steps
}


// Do we set properties when initializing?
class Recipe {
	constructor(ingredients, steps) {…}
}


// Do we allow setting properties after initializing?
class Recipe {
	setIngredients(ingredients) {…}
	setSteps(steps) {…}
}


// Do we allow both?
class Recipe {
	constructor(ingredients, steps) {…}
	setIngredients(ingredients) {…}
	setSteps(steps) {…}
}


// Recipes are made by chefs
class Chef {
	makeRecipe(recipe) {…}
}


// Does the chef turn on the oven?
class Chef {
	makeRecipe(recipe) {
		this.oven.turnOn();
		recipe.make();
	}
}


// Does the recipe turn on the oven only when needed?
class Chef {
	makeRecipe(recipe) {
		recipe.make(this.oven);
	}
}
class Recipe {
	make(oven) {
		if (this.needsOvenPreheated) {
			oven.turnOn();
		}…
	}
}


// Which class do we add the purchase ingredients method to?
purchaseIngredients(ingredients) {…}

class Chef {
	purchaseIngredients(ingredients) {…}
}
class Manager {
	purchaseIngredients(ingredients) {…}
}
class Ingredients {
	purchase() {…}
}
class Shop {
	purchase(items) {…}
}

Focus on what, not how

  • How: Step by step process to get a solution
  • What: Declaring just the key parts of my solution
  • Why: Solving the actual problems of my users

Two hardest things

  • Naming
  • Cache invalidation

Components force teams to tackle both!

Components add meaning, remove fluff

  • Components are the new semantic layer from <PrimaryButton> to <App>
  • I They are easy to change
  • They are easy to delete
  • They are easy to divide up
  • They are easier to debug
  • They represent meaningful concepts

The Component Model

  • Define your component and the system runs them
  • Initially: runs component from top-to-bottom
  • When user taps: runs top-to-bottom
  • When API request succeeds: runs top-to-bottom
  • When API request fails: runs top-to-bottom
  • When any state changes: runs top-to-bottom

Methods vs Messages

JavaScript classes allow calling methods

class Counter {
  add(amount) {
    // Code I write
  }
}

const counter = new Counter();
// Call add method.
counter.add(1);
Objective-C classes allow sending messages
// Send add message.
[counter add:@1];

// Here is the identifier for this message.
SEL messageIdentifier = @selector(add:);

// Send add message.
[counter performSelector:@selector(add:) withObject:@1];

// Create message ready to send.
// Yes, it is a fair amount of code…
NSMethodSignature *signature = [Counter
  instanceMethodSignatureForSelector:@selector(add:)];
NSInvocation *sender = [NSInvocation
  invocationWithMethodSignature:signature];
NSNumber *amount = @1;
[sender setArgument:&amount atIndex:2];
[sender setTarget:counter];

// Now, let’s send the add message twice.
[sender invoke];
[sender invoke];

Message Generators

  • Send: yield "some string"
  • Receive: const reply = yield "string"
  • Result: return "final message"
function* GenerateSomething() {
  // Send a message.
  yield "abc";

  // A message can be a primitive: string, number, regex, symbol.
  yield "abc";
  yield 42;
  yield /[a-z]+/;
  yield Symbol.for("identifier");

  // A message can be a collection: array, set, map, object.
  yield ["abc", "def"];

  // A message can be another generator function.
  yield OtherGenerator;

  // Receive a message reply.
  const reply = yield "abc";

  // Return a final message.
  // You can use the result of replies!
  return { a: "final result", b: reply };
}

Message Processors

  • They accept a generator function
  • They initialise some sort of state
  • They iterate though the generator, receiving messages
  • They check if they recognise the message
  • Unrecognised messages are ignored, so skip to next message
  • Recognised messages are processed
  • e.g. Match regex, add number, append string, fetch URL.
  • The state might change due to processing the message
  • e.g. String was appended, number changed.
  • Reply to messages that expect one
  • e.g. Return regex matches or fetch reponse.
  • They keep iterating though the generator until it’s done
  • They might return the state or some sort of result

Functions can be messages too

Parser component

import { parse, mustEnd } from 'yieldparser';

function* IPAddress() {
  const [firstString] = yield /^d+/;
  const firstDigit = parseInt(firstString, 10);
  yield '.';
  const [secondString] = yield /^d+/;
  const secondDigit = parseInt(secondString, 10);
  yield '.';
  const [thirdString] = yield /^d+/;
  const thirdDigit = parseInt(thirdString, 10);
  yield '.';
  const [fourthString] = yield /^d+/;
  const fourthDigit = parseInt(fourthString, 10);
  yield mustEnd;
  return [firstDigit, secondDigit, thirdDigit, fourthDigit];
}

parse('1.2.3.4', IPAddress());
/*
{
  success: true,
  result: [1, 2, 3, 4],
  remaining: '',
}
*/

Nested parser components

import { parse, mustEnd } from 'yieldparser';

function* Digit() {
  const [digit] = yield /^d+/;
  const value = parseInt(digit, 10);
  // Validate digit is inclusively between 0 and 255.
  if (value < 0 || value > 255) {
    return new Error(`${value} must be >= 0 && <= 255`);
  }
  // The number value is the result from this component.
  return value;
}

function* IPAddress() {
  const first = yield Digit;
  yield '.';
  const second = yield Digit;
  yield '.';
  const third = yield Digit;
  yield '.';
  const fourth = yield Digit;
  yield mustEnd;
  return [first, second, third, fourth];
}

parse('1.2.3.4', IPAddress());
/*
{
  success: true,
  result: [1, 2, 3, 4],
  remaining: '',
}
*/

HTML Renderer

HTML Component

Component Source
function* HTMLComponent2() {
  yield HTML`<h1>Hello!</h1>`;
  yield HTML`<ul>`;
  yield HTML`<li>Some`;
  yield HTML`<li>list`;
  yield HTML`<li>items`;
  yield HTML`</ul>`;
}

HTML Output

55 bytes

<h1>Hello!</h1>
<ul>
<li>Some
<li>list
<li>items
</ul>
  • Hello!
  • Some
  • list
  • items

HTML Processor

function* processHTML(generator) {
  for (const message of generator()) {
    if (message.type === identifiers.html) {
      yield message.raw;
      yield "\n";
    }
  }
}

Convert to a single string

const generator = processHTML(HTMLComponent);
const htmlString = Array.from(generator).join("");

Convert to readable stream

const generator = processHTML(HTMLComponent);
const sream = new ReadableStream({
  pull(controller) {
    const { value, done } = generator.next();

    if (done) {
      controller.close();
    } else {
      controller.enqueue(value);
    }
  }
});
const response = new Response(stream, {
  headers: { "Content-Type": "text/html" }
});

Parser

AWS Region Parser

function* ParseAWSRegion() {
  const primary = yield [
    "us-gov",
    "us",
    "af",
    "ap",
    "ca",
    "eu",
    "cn",
    "me",
    "sa"
  ];
  yield "-";
  const secondary = yield [
    "northeast",
    "northwest",
    "southeast",
    "central",
    "north",
    "east",
    "west",
    "south"
  ];
  yield "-";
  const digit = yield [1, 2, 3];
  return {
    primary,
    secondary,
    digit
  };
}

Results

parseString(
  "us-west-1",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "us",
    "secondary": "west",
    "digit": 1
  },
  "remaining": ""
}
parseString(
  "ap-southeast-2",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "ap",
    "secondary": "southeast",
    "digit": 2
  },
  "remaining": ""
}
parseString(
  "xx-east-1",
  ParseAWSRegion
)
{
  "success": false,
  "failedOnMessage": [
    "us-gov",
    "us",
    "af",
    "ap",
    "ca",
    "eu",
    "cn",
    "me",
    "sa"
  ],
  "remaining": "xx-east-1"
}
parseString(
  "eu-west-3",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "eu",
    "secondary": "west",
    "digit": 3
  },
  "remaining": ""
}
parseString(
  "us-gov-west-1",
  ParseAWSRegion
)
{
  "success": true,
  "result": {
    "primary": "us-gov",
    "secondary": "west",
    "digit": 1
  },
  "remaining": ""
}

Parser Processor

function parseString(input, generator) {
  let reply;
  const gen = generator();
  mainLoop:
    while (true) {
      const result = gen.next(reply);
      if (result.done) {
        return Object.freeze({
          success: true,
          result: result.value,
          remaining: input
        });
      }
      const message = result.value;
      const matchers = new Array().concat(message);
      for (let matcher of matchers) {
        if (typeof matcher === "string" || typeof matcher === "number") {
          const searchString = matcher.toString();
          let found = false;
          const newInput = input.replace(searchString, (_1, offset) => {
            found = offset === 0;
            return "";
          });
          if (found) {
            reply = matcher;
            input = newInput;
            continue mainLoop;
          }
        } else if (matcher instanceof RegExp) {
          if (["^", "$"].includes(matcher.source[0]) === false) {
            throw new Error(`Regex must be from start: ${matcher}`);
          }
          const match = input.match(matcher);
          if (match) {
            reply = match;
            input = input.slice(match[0].length);
            continue mainLoop;
          }
        }
      }
      return Object.freeze({
        success: false,
        failedOnMessage: message,
        remaining: input
      });
    }
}

State Machine

State machine components

import { entry, on, start } from "yieldmachine";

// This machine will load the passed URL.
function FetchMachine(url) {
  function fetchData() {
    return fetch(url);
  }
  
  function* idle() {
    yield on("FETCH", loading);
  }
  function* loading() {
    yield entry(fetchData);
    yield on("SUCCESS", success);
    yield on("FAILURE", failure);
  }
  function* success() {}
  function* failure() {
    yield on("RETRY", loading);
  }

  return idle;
}

// This machine will load our API.
function ExampleAPILoader() {
  const exampleURL = new URL("https://api.example.org/…");
  return FetchMachine(exampleURL);
}

// Start our API loader machine.
const loader = start(ExampleAPILoader);
loader.current; // "idle"

// Send the FETCH message to start loading.
loader.next("FETCH");
loader.current; // "loading"

loader.results.then((results) => {
  // Use response of fetch()
  console.log("Fetched", results.fetchData);
  loader.current; // "success"
});

Multicolored component

Component

function* Example() {
  yield HTML`

Hello!

`; yield HTML``; yield HTML`

This is HTML

`; yield RemoteImage("https://images.unsplash.com/photo-1530281700549-e82e7bf110d6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80", "dog playing at the seaside"); yield RemoteImage("https://i.imgur.com/GleAY3fl.jpg", "a tiger cub and its parent"); yield CSS("p", "color: red;"); yield CSS("p:after", "color: deepskyblue; content: ' and this is CSS';"); }

Output Preview

Hello! Home About us Terms This is HTML

simple web page with the text "Hello! Home About us Terms This is HTML" and an image of a dog playing at the seaside and an image of a tiger cub and its parent

Output

const htmlOutput = processRichHTML(Example);
const cssOutput = processCSS(Example);
const links = Array.from(allLinks(Example));
const invalidLinks = Array.from(validateLinks(links));

HTML Output

<h1>Hello!</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About us</a>
<a href="/terms">Terms</a>
</nav>
<p>This is HTML</p>
<img src="https://images.unsplash.com/photo-1530281700549-e82e7bf110d6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80" alt="dog playing at the seaside">
<img src="https://i.imgur.com/GleAY3fl.jpg" alt="a tiger cub and its parent">

HTML: 408 bytes

CSS Output

output p {color: red;}
output p:after {color: deepskyblue; content: ' and this is CSS';}

CSS: 89 bytes

All Links

[
  "/",
  "/about",
  "/terms"
]

Invalid Links

function validateLinks(links) {
  const knowLinks = new Set([
    "/",
    "/about",
    "/hiring",
    "/privacy",
    "/blog",
    "/features"
  ]);
  const invalidLinks = new Set();
  for (const link of links) {
    if (knowLinks.has(link))
      continue;
    invalidLinks.add(link);
  }
  return invalidLinks;
}
[
  "/terms"
]

Meta Links HTML

function* allLoadedOrigins(generator) {
  for (const message of generator()) {
    if (message.type === identifiers.remoteImage) {
      const url = new URL(message.url);
      yield url.origin;
    }
  }
}
function* processMetaLinkHTML(generator) {
  for (const origin of allLoadedOrigins(generator)) {
    yield HTML`<link rel="dns-prefetch" href="${origin}">`;
  }
}
[
  "https://images.unsplash.com",
  "https://i.imgur.com"
]
<link rel="dns-prefetch" href="https://images.unsplash.com">
<link rel="dns-prefetch" href="https://i.imgur.com">

React popularised:

view = f (data)

Why don't we have?

route = f(url)
apiData = f(route)
apiRequest = f(form)
sanitizedData = f(untrustedInput)
state = f(events)
animationFrame = f(time)
cssVariables = f(tokens)
cssRules = f(selector)
localizedText = f(key, values)

JavaScript is Everywhere

  • Browsers: Firefox, Edge, Safari, Chrome
  • Cloud: Node.js, Lambda, Containers
  • Edge: Cloudflare Workers, Deno Deploy, Fastly

Generators have amazing support

shows a list with each of the items with a green tick indicating support for Generators

  • Modern Browsers
  • Modern Cloud Servers
  • TypeScript & JSDoc
  • Modern IDEs like VS Code
  • Code formatters & linters

shows a list with the single item with an orange cross indicating no support for Generators

  • Internet Explorer (except via Babel)

JavaScript is Everywhere

  • Browsers: Firefox, Edge, Safari, Chrome
  • Cloud: Node.js, Lambda, Containers
  • Edge: Cloudflare Workers, Deno Deploy, Fastly

Generators have amazing support

shows a list with each of the items with a green tick indicating support for Generators

  • Modern Browsers
  • Modern Cloud Servers
  • TypeScript & JSDoc
  • Modern IDEs like VS Code
  • Code formatters & linters

shows a list with the single item with an orange cross indicating no support for Generators

  • Internet Explorer (except via Babel)

Open Source Libraries

Learn More!

I write about generator functions, accessibility, modern web technologies, and design.

Thanks to

  • Remix — React framework
  • Hero Icons
  • Preparing for Tech Talk by Dan Abramov

Please reach out!

@concreteniche on Twitter @RoyalIcing on GitHub