It’s always fun when the MDN documentation doesn’t tell us what the options are within the methods page, I you have to dig deeper into the constructor. The TypeScript bindings don’t always make sense.
Anyways, I needed a datetime string with the date, time in hours and minutes, and the timezone. I’ve made the timezone explicit and came up with this snippet.
While both are true, I would rather have the shorthand, as that’s what we have strictly for eslint rules. This Github Issue helped describe the solution on how to implement in your transform file.
I forked a small demo from Wes Bos and ran in on my own sandbox. It seems much cleaner than using a script tag per each dependency, and works on all major browsers.
In my little demo app, I placed the importmap in the head of the document. I’m using react as well as my own utils file to test out the functionality.
From there, I imported these libraries in a script module.
<body> <div id="app"></div> <script type="module"> /* eslint-disable */ import { useState } from "react"; import { createRoot } from "react-dom"; import { formatMoney } from "utils"; createRoot(document.querySelector(`#app`)).render(formatMoney(100.2365)); </script></body>
I had a hiccup with [plugin:vite
] as I found countless others have, so I wrote a custom bun server to host this project. Hopefully there’s a better setup I can find with Vite using importmap in the future.
Luxon datetime library defaults startOf and endOf methods to UTC. But this isn’t great for end users who don’t live in UTC / GMT. To offset this, we need to grab the time zone offset from the user’s system.
While Kelvin talks about the benefits of using Sails, I was thinking about the other “boring” things that constitute a boring stack. It goes beyond tecnologies, like how to run a business, how to organize your team, and how to market the product. And when those things are boring, they are unsexy and oftentimes neglected.
A feature flag is a decision point in your code that can change the behavior of your application.
Temporary flags are often used to safely deploy changes to your
application or to test new behaviors against old ones. After a new behavior
is being used by 100% of your users, the flag is intended to be removed.
Traffic light showing a green light. Unsplash - Eliobed Suarez
Permanent flags give you a way to control the behavior of your
application at any time. You might use a permanent flag to create a
kill-switch or to reveal functionality only to specific users.
Traffic light showing a green light. Unsplash - Eliobed Suarez
Feature Flags Are Context Sensitive
The code path token can change based on the context provided; for example, the user’s identity, the plan they’ve paid for, or any other data.
Feature Flags Are Deployment Agnostic
Feature flags can be used to control which users can see each change. This decouples the act of deploying from the act of releasing.
Use Cases for Flags
Release - A temporary flag that initially serves false to all targets, then progressively rolls out true to targets until it reaches 100%
Kill Switch - A permanent safety mechanism used to shut off non-core functionality or third-party tools in an emergency
Experiment - A flag to test a hypothesis and provide continuous optimization using beta users and A/B tests
Migration - A temporary flag used to migrate data or systems while keeping your application available and disruption free
These are the ones advertised by LaunchDarkly. There are many more use cases, and they allow for customization.
What is LaunchDarkly?
LaunchDarkly is a company that provides feature flags as a service.
At work, flags are served and managed by LaunchDarkly’s services.
LaunchDarkly is integrated through streaming via web sockets, meaning each web app user session receives messages only when necessary. Changes can be pushed out to clients in real time.
A diagram showing the end-to-end connection between LaunchDarkly’s flag
delivery network and your application.
From here, we usually add conditional logic to toggle the flag logic to the target code.
We can also use the same method to do the same on the server-side as well.
Adding User Context
LaunchDarkly uses context to target which flags should be on or off.
The user’s context, known in LaunchDarkly as identity, must be used when initializing the application.
Anonymous Users
On initialization, a user session will be identified as an anonymous user.
This is because in the initialization, the user’s session token (access token) has not been verified yet.
Because we want to use flags for public users as well, i.e. those not logged into the app, we initialize the Provider before the token check.
An anonymous user has two attributes:
key - a unique identifier for the anonymous user and a field on the ldUser type.
anonymous - a field on the ldUser type that notes if a user is anonymous or not. Stored as a Boolean.
Authenticated Users
Once the user authenticates, and the application has retrieved the user context,
we identifies the user using the ldClient.identify function.
In this identify function, we pass along the following information about our user:
key: Our user’s unique identifier (as a uuid)
anonymous: false
email: the user’s email address
In addition, we pass along some custom fields that we can use to narrow down the user’s targeting.
This includes their role and their organization, since our app is a multi-tenant.
If this user has already been added to LaunchDarkly, their flag profile will be returned.
If this user is new, LaunchDarkly will automatically create this user, create their flag profile, and be returned.
Logout
On logout, the application re-identifies the user using the ldClient.identify function.
Since the application has a logout hook, we add a handler to identify the user to be an anonymous user again.
This resets all flags to switch over for anonymous users.
When do we change our flags?
Per Release - For each release, our release team has a list of flags to modify, including toggling targeting and changing rules and segments per rules.
On Market Support - There may be a request to make a non-release changes. These include new customer onboarding and turning on a feature for an existing customer.
Deployments
There are different types of deployments:
Canary Releases - User groups who would like to opt in
Ring Deployments - Different user segments at a time - e.g. beta or power users
Percentage-based Deployments - Start with low percentage, then move to higher. For operational changes
Each of these can be implemented using feature flags.
Feature flags and blue/green deploys are complementary techniques. Although there are areas of overlap, each approach has distinct benefits, and the best strategy is to use both.
Testing
It isn’t necessary (or even possible) to test every combination of feature flags. Testing each variation of a flag in isolation (using default values for the other flags) is usually enough, unless there’s some known interaction between certain flags.
Here’s an example using jest and LaunchDarkly’s mock testing library.
import { mockFlags } from 'jest-launchdarkly-mock';it('tests with the flag on' , () => { mockFlags({ [FLAG_IN_QUESTION]: true, }); // Write your test here});it('tests with the flag off' , () => { mockFlags({ [FLAG_IN_QUESTION]: false, }); // Write your test here});
Flag Maintenance
Cleaning up flags aggressively is the key to preventing technical debt from building up. There’s no royal road to flag cleanup, but there are some processes that make it manageable.
A stale flag is a temporary flag that is no longer in use and has not been cleaned up. Too many stale flags are a form of technical debt and an antipattern that you should avoid.
At work, we follow a practice to do it prior to any major release. This is about every 3 to 4 months.
Questions you should be able to answer now
What is feature management?
A decision point in your code that can change the behavior of your
application.
What is the difference between a temporary vs. a permanent flag?
A temporary flag will ultimately be removed from the application. A
permanent won’t and will stay as a kill switch.
How do flags configured in LaunchDarkly get delivered to our applications?
LaunchDarkly uses streaming via web sockets.
How can flag management be resilient to failures?
LaunchDarkly has multiple fallbacks. If their CDN goes down, it goes to
their service. If their service goes down, you can route it to an
external service like their relay proxy. If all of those go down, then
there will be cached results from the last sucessful response. And if
this is a first time request, then a fallback default value is used.
I wrote a script that updates my daily notes to add properties. Since I’ve been using Obsidian for awhile, properties was introduced much later. I’ll walk through my code.
// Get the current file title, which is in a common date format: YYYY-MM-DDconst currentFileTitle = tp.file.title;// Set folder you want to get latest file for hereconst folder = "Calendar/Personal Reviews/journal and daily review/2022";// Get all files in that folder, including nested foldersconst filesInFolder = app.vault.getMarkdownFiles().filter(file => { return file.path.startsWith(folder);});// Sort files by file namefilesInFolder.sort((a, b) => a.basename < b.basename ? 1 : -1);// Get the index of the current fileconst currentIndex = filesInFolder.findIndex(file => file.basename === currentFileTitle);// Get basename of previous and next TFiles to be used in linklet previousEntry = '';let nextEntry = '';// Wrap it around a try catch block in case there's something wrong with getting these basenamestry { previousEntry = `[[${filesInFolder[currentIndex + 1].basename}]]`} catch (err) { console.error(err);}try { nextEntry = `[[${filesInFolder[currentIndex - 1].basename}]]`} catch (err) { console.error(err);}
Here’s my template that the templater plugin uses in markdown.
<%*This is where the JS code above is inserted-%>---tags: - logs/daily created: <% currentFileTitle %>previousEntry: - "<% previousEntry %>"nextEntry: - "<% nextEntry %>"---
I’ve had to modify this depending if the file already has properties or not.
I was getting some errors in my unit test output because I converted my SVGs to React components using SVGR.
As such, I needed to re-configure Jest accordingly.
Create the mock file
export default "svgr-div"; // This is the kebab case that jest is looking forexport const ReactComponent = "div";// @see https://react-svgr.com/docs/jest/ for setup
I keep a list of projects I want to work on called “Someday Builds”.
Many of these are bespoke apps that I know could help me, like an automation.
However, automations take time to build, and typically I’ll be able to finish
that task far faster than I can build the automation to reduce that overhead.
Case in point, I wanted to build a chrome extension that by running the
extension, the clipboard takes the head title and the full, cleaned link,
and copies it in markdown format. The reason I want this is to add quick links
to my daily notes without having to find the title of the article, put it in
the markdown format, grab a clean link, then add that to the parentheses in the
markdown format. Also, Obsidian loves to grab headings, so I will get a # symbol
for the description of the link. It’s a few extra keystrokes, and really not
worth the efforts (about an extra few seconds of my day, plus some extra effort).
Of course, the time to build is reduced to an hour of work with the help of
ChatGPT.
Prompt
I want you to act as a programmer. You have a deep knowledge of writing chrome extensions and understand the best practices. I will provide the purpose of the application and a feature set. If there are any ambiguities in the features, please pause in the next step and clarify questions using a numerical list format. Next, you will come up with the basic outline for the code as pseudocode before writing the program. The next message you will ask me is what the feature set is.
Want to build a chrome extension that by running the extension, the clipboard takes the head title and the full, cleaned link, and copies it in markdown format.
The chrome extension takes the webpage metadata and extracts the title. Then, it takes the URL. The output should be a string that is copied to the clipboard in markdown format as follows [Title](Link). I want the link to be clean, so no url queries like utm should be added. We can also register a shortcut that can trigger the extension. There should be a settings menu to modify this shortcut. Also, the settings menu should have a modifier for the output format. The name of this extension should be called “Copy Link to Markdown”.
Installing the Chrome Extension Locally
It’s been awhile since I’ve done this, so I Google searched an article to help me.
I used the Chrome dev team’s hello world extension as a guide.
Developing the Extension
The first generated codebase did not work. Of course, my prompt above says to pseudocode it first, which was a struggle because there were actual code snippets with a bunch of comments where the code should be. So I had to continue prompting it to give me the final code.
After about 15 minutes, I got the code working. That was after passing it back and forth a few errors and working through those one at a time. Then I saw opportunities everywhere of what I wanted it to do next, as well as places I knew where the codebase needs to be refactored.
I made it generate an image for the extension icon.
A screenshot of the extension in action
For whatever reason, the extension shortcut key needed to be set differently, so
I had to use a different menu for that.
Final Thoughts
I placed the extension on Github where you can play with it today. There’s a lot more work I need to do to make this extension publishable. But for a v1, it’s good enough.
The total development time was a little over an hour, and I’m happy about that because I’m already using it. And I can tell it makes a huge difference because I save links all of the time in my daily notes.
I needed a regular expression for checking if a number has 1 decimal point.
Here’s the regular expression ChatGPT came up with: /^\d+(\.\d{1})?$/
Of course, it wasn’t actually the correct requirement.
It needed to check if it’s an integer (no decimal points).
And if it is a decimal point, it can be at most one.
I screwed up and tried to pair two quantifiers together.
So if you have ? and you want to use {0,1}, you can’t do {0.1}?.
And I didn’ need the group, although in retrospect, using the quantifier on the
group works, but removing the group makes the ? obsolete.
Here’s what I ultimately came up with.
function validateToOneOrNoDecimalPoint(number) { // Regular expression to match numbers with either 0 or 1 decimal point (optional) const regex = /^\d+\.?\d{0,1}$/; return regex.test(number);}// Example usage:console.log(validateToOneOrNoDecimalPoint(3.5)); // trueconsole.log(validateToOneOrNoDecimalPoint(10)); // trueconsole.log(validateToOneOrNoDecimalPoint(2.34)); // false (more than one decimal point)console.log(validateToOneOrNoDecimalPoint(5.)); // true (no decimal point)console.log(validateToOneOrNoDecimalPoint(5)); // true (no decimal point)
And I added even more test cases, and used regex.match over regex.test.
Subtle difference.
I have a bunch of files I want to rename using the rename utility.
If you’ve installed the rename utility on your Mac, you can use it to mass rename files in the terminal. The rename utility typically follows a specific syntax, and it’s quite powerful for renaming multiple files according to certain patterns or rules.
Here’s a basic example of how you can use the rename utility:
When dealing with filenames that contain spaces, you’ll need to escape those spaces in your command. Here’s how you can do it using the rename command:
rename 's/ Clear Labs//' *.md
This command will remove the substring ” Clear Labs” (including the space) from all .md files in the current directory.
Svelte is a component-based JavaScript framework that compiles your code at build time rather than at runtime. This means that instead of shipping a bulky framework to the client, Svelte generates highly optimized vanilla JavaScript code that updates the DOM. This approach results in faster load times and better performance. Additionally, Svelte has a small API surface area, making it easy to learn and use.
The following example is a simple counter application that teaches the basics of Svelte.
<script> let count = 0; function increment() { count += 1; } function decrement() { count -= 1; }</script><main> <h1>The count is {count}</h1> <button on:click={decrement}>-</button> <button on:click={increment}>+</button></main><style> main { text-align: center; padding: 1em; } button { margin: 0 1em; padding: 0.5em 1em; }</style>
The count is 0
Reactive Variables: let count = 0; This variable is reactive. Any changes to count will automatically update the DOM wherever count is referenced.
Event Handling: The on:click directive is used to attach click event listeners to both buttons, calling the increment and decrement functions accordingly.
Inline Handlers: If you prefer, you could inline these functions directly in the on:click handler for something even simpler, like on:click={() => count += 1}.
Conditional Rendering: Suppose you want to display a message when the count is above a certain threshold. You can use an {#if} block directly in your HTML:
{#if count > 10} <p>You have reached a count greater than 10!</p>{/if}
Svelte Components
Expanding our simple counter application by breaking it down into smaller components, which is a common practice for improving the organization and reusability of your code in larger applications. We’ll create two components: CounterDisplay for showing the current count and CounterButton for the increment and decrement buttons.
Create a new file named CounterDisplay.svelte in the src directory. This component will be responsible for displaying the count.
<script> // This component accepts a prop named `count` export let count;</script><h1>The count is {count}</h1>
Create another file named CounterButton.svelte in the src directory. This component will represent a button that can increment or decrement the counter.
<script> // The component accepts two props: the button text and the click action export let text; export let handleClick;</script><button on:click={handleClick}>{text}</button>
Now, update the App.svelte file to use these new components.
<script> import CounterDisplay from './CounterDisplay.svelte'; import CounterButton from './CounterButton.svelte'; let count = 0; function increment() { count += 1; } function decrement() { count -= 1; }</script><main> <CounterDisplay {count} /> <CounterButton text="-" handleClick={decrement} /> <CounterButton text="+" handleClick={increment} /></main><style> main { text-align: center; padding: 1em; }</style>
Props: Components in Svelte can accept “props”, which are custom attributes passed into components. In our case, CounterDisplay accepts a count prop, and CounterButton accepts text and handleClick props. This allows the components to be reusable and dynamic. Props are reactive, where any prop changes will trigger changes in the component.
Component Interaction: The App.svelte component manages the state (count) and functions (increment and decrement) and passes them down to the child components. This demonstrates a fundamental pattern of component-based architecture: lifting state up and passing data and behavior down through props.
Event Handling in Child Components: The CounterButton component receives a function (handleClick) as a prop and attaches it to the button’s click event. This is a common pattern for handling events in child components and allowing parent components to define the behavior.
Slots in Svelte
Slots in Svelte allow you to create components that can accept content dynamically from their parents. This is similar to transclusion or content projection in other frameworks. In other libraries like React, this is handled with children. Slots make components more flexible by letting you inject content into predefined places within a component’s template.
Basic Slot Example
Imagine you’re building a Card component that you want to reuse across your application, but with different content each time.
Card.svelte:
<div class="card"> <slot></slot> <!-- This is where the parent's content will be injected --></div><style> .card { border: 1px solid #ccc; border-radius: 8px; padding: 20px; margin: 10px 0; }</style>
You can use this Card component in a parent component and pass in different content like so:
<script> import Card from './Card.svelte';</script><Card> <h2>Title</h2> <p>This is some card content.</p></Card><Card> <p>Another card with different content.</p></Card>
Named Slots
Svelte also supports named slots, which allow you to define multiple slots within a single component.
Card.svelte updated with named slots:
<div class="card"> <header> <slot name="header"></slot> <!-- Named slot for header content --> </header> <slot></slot> <!-- Default slot for main content --> <footer> <slot name="footer"></slot> <!-- Named slot for footer content --> </footer></div>
Module scripts in Svelte introduce a powerful feature for managing reusable code and behaviors in your components. A module script runs once when a component is first imported, rather than each time a component instance is created. This makes it ideal for defining shared logic, helpers, and stores that can be used across all instances of a component.
To define a module script in a Svelte component, you use the <script context="module"> tag. Anything declared inside this tag is scoped to the module, not to individual instances of the component. This is particularly useful for situations where you want to maintain a shared state or perform actions that only need to happen once, regardless of how many times a component is instantiated.
Here’s a simple example:
Counter.svelte:
<script context="module"> // This count is shared across all instances of Counter.svelte let count = 0; export function increment() { count += 1; console.log(count); }</script><script> // This script block is for instance-specific logic and state import { onMount } from 'svelte'; onMount(() => { // Call the shared increment function when the component mounts increment(); });</script><p>This component has been instantiated.</p>
In this example, the increment function and the count variable are defined in a module script and shared across all instances of Counter.svelte. Every time a new instance is created, it logs the incremented count, demonstrating that count is shared and persists across instances.
Use Cases for Module Scripts
Defining shared utility functions: For components that require common functionality, you can define utility functions in a module script to avoid duplicating code.
Creating singleton stores: If you need a store that’s shared across all instances of a component, defining it in a module script ensures that you have a single, shared store.
Optimizing performance: Since code in a module script is executed only once, it’s an excellent place for performing expensive operations like setting up subscriptions or fetching data that should be done once per component type, rather than once per instance.
Advanced Component Composition
Module scripts complement Svelte’s component composition model by allowing you to abstract and share logic effectively. For instance, you can combine module scripts with slots, props, and context to create highly reusable and customizable components.
Imagine a scenario where you’re building a library of UI components. Using module scripts, you can provide a consistent API for configuring these components globally (like themes or internationalization settings) while using instance scripts for configuration that is specific to a component’s use case.
Considerations
Scoping: Remember that variables and functions declared in module scripts are not directly accessible in the instance script or the component’s markup. To use them in the component, they need to be exported from the module script and then imported or used in the instance script.
Singleton behavior: Since the module scope is shared across instances, be mindful of side effects that might occur when modifying shared state. This is similar to how static variables would behave in class-based languages.
Stores for State Management
Svelte Stores takes a reactive approach to state management, which is different than Redux. At the core of it, see the following.
Creating a Store: The simplest form of a store in Svelte is a writable store. For example, creating a store to hold a number could look like this:
import { writable } from "svelte/store";const count = writable(0);
Subscribing to a Store: To access the value of a store outside of a Svelte component, you subscribe to it. This might be where syntax can get tricky. Use the $: syntax intuitive for reactive statements. Alternatively, you can directly subscribe using .subscribe method.
Updating Store Values: Svelte provides a few patterns for updating the store’s value, such as set, update, and using the auto-subscription feature within components with the $ prefix.
Reactivity: One of the powerful features of Svelte is its built-in reactivity. Reactive variables automatically propagate through your application.
Derived Stores
Derived stores let you create a store based on other store(s), automatically updating when the underlying stores change. This is useful for calculating derived values or aggregating data from multiple sources.
Example:
Suppose you have a store for a list of items and another derived store that calculates the total number of items.
For applications with complex state, consider structuring your store as a JavaScript object or even using a custom store. Custom stores can encapsulate more complex behavior, such as asynchronous operations or integration with external data sources.
Creating a Custom Store:
function createCustomStore() { const { subscribe, set, update } = writable(initialValue); return { subscribe, // Custom methods to interact with the store increment: () => update((n) => n + 1), decrement: () => update((n) => n - 1), reset: () => set(initialValue), // More complex operations... };}
Slots and advanced state management techniques in Svelte offer a combination of simplicity and power, enabling you to build complex and dynamic applications with less code and more declarative, readable structures.
Actions
Svelte actions are a powerful and somewhat under-appreciated feature that provide a neat way to interact with DOM elements directly. Actions allow you to attach reusable behavior to DOM elements, which can be especially useful for integrating third-party libraries or creating custom directives that enhance your application’s functionality without cluttering your components with imperative code.
An action is simply a function that is called when a DOM element is created, and it returns an object that can contain lifecycle methods like update and destroy. Here’s a basic outline of how actions work:
Creating an Action: To create an action, you define a function that takes at least one argument, the element it’s attached to. This function can return an object with optional update and destroy methods. The update method is called whenever the parameters of the action change, and destroy is called when the element is removed from the DOM.
Using an Action: You apply an action to a DOM element in your Svelte component using the use directive.
Here’s a simple example to illustrate:
Defining a Simple Action
Let’s say you want to create an action that automatically focuses an input element when the component mounts:
// focus.jsexport function autofocus(node) { // Directly focus the DOM node node.focus(); // No need for update or destroy in this case, but they could be added if necessary}
Applying the Action in a Component
You can then use this action in any component like so:
<script> import { autofocus } from './focus.js';</script><input use:autofocus />
This example is quite basic, but actions can be much more complex and powerful. For instance, you could create an action to:
Implement drag-and-drop functionality by attaching mouse event listeners to the element.
Integrate with third-party libraries, such as initializing a date picker on an input element.
Add custom animations or transitions that are not easily achieved through Svelte’s native capabilities.
Svelte makes adding animations and transitions to your web applications straightforward and intuitive, enhancing user experience with visual feedback and smooth transitions. Let’s explore how to add a simple fade transition to elements in a Svelte application, and then we’ll look at a more interactive example.
Transitions and Animations
Svelte’s transition functions, such as fade, are part of the svelte/transition module. To use a fade transition on an element, first import fade from this module.
Example:
<script> import { fade } from 'svelte/transition'; let isVisible = true;</script><button on:click={() => (isVisible = !isVisible)}> Toggle</button>{#if isVisible} <div transition:fade={{ duration: 300 }}> Fade me in and out </div>{/if}
In this example, clicking the “Toggle” button shows or hides the div element with a fade effect over 300 milliseconds.
Fade me in and out
Interactive Animation Example
For a more interactive example, let’s create a list where items can be added and removed, each with an animation. We’ll use the slide transition to make items slide in and out.
First, add slide to your imports:
<script> import { slide } from 'svelte/transition'; let items = ['Item 1', 'Item 2', 'Item 3'];</script>
Now, create a function to add a new item and another to remove an item:
function addItem() { items = [...items, `Item ${items.length + 1}`];}function removeItem(index) { items = items.filter((_, i) => i !== index);}
Finally, render the list with transitions on each item:
In the #each block, we use the slide transition to animate the addition and removal of list items. The (item) key ensures that Svelte can uniquely identify each item for correct animation, especially during removals.
Item 1
Item 2
Item 3
Customizing Transitions
Svelte’s transitions can be customized extensively via parameters. For both fade and slide, you can adjust properties like duration, delay, and easing (to control the animation’s timing function). Svelte also supports custom CSS transitions and animations, giving you complete control over your animations’ look and feel.
Using the Basic Svelte Template
If you’re looking for something simpler or specifically want to work with just the Svelte library without the additional features offered by SvelteKit, you can start with the basic Svelte template.
I created a gist
for it. The original script did not have much error handling. Something to expand
on in the future is to have that error handling within Obsidian, which I would
like to see.
Also, I used the suggester API
to give the user options on which returned item they care about using the Google
Books API.
And here’s my personal template using Obsidian properties.
I was listening to the latest Latent Space podcast with Steve Ruiz of tldraw. (You can watch the full video below).
What I was taken back by is the “Make Real” demo that included different approaches to using GPT4-Vision.
Of course, my first instinct was to play test it and see what I could make and where the limitations are.
As a part-time designer, I was curious to see a working demo of a wireframe I had in mind.
Demo
“Make Real” outputs in HTML, CSS, and JS (with tailwind), and I was able to cobble together this little demo of telling you the timezones based off of the selected input.
One thing to note is the component below is actually a Svelte component, which I created using ChatGPT to convert the HTML to Svelte.
And of course, I’ve made slight modifications so there are more timezones than the ones given to me.
System Timezone:
Selected Timezone:
Design aesthetic aside, this is a fully functional prototype that I could reuse and prettify.
With a little bit of coding knowledge, I can be off racing towards putting this in a production-ready app.
Of course, the way to make these modifications are easier if you have some implementation knowledge of what you’re doing.
Thoughts on Make Real
It’s not an career killer for front-end engineers. We are far from that.
Instead, these should be seen as tools to augment our work and allow us to spend more energy playing over mulling over small implementation details that might get changed later.
With each iteration, I was able to get “Make Real” to output different variations of the same idea, to really see what the realm of possibilities are.
There’s a balance though of giving annotations and adding or subtracting more to the drawings.
For example, when I asked it to give me 50 timezone selections from the most populated areas on the earth, it only gave me 10 items.
And even then, ChatGPT came up with TZs not in use.
In a different iteration, I wanted an explorable map.
When I asked it to give me a map of where this selected timezone was, it gave me a placeholder. Not so helpful.
The following is the video with Steve Ruiz, and I highly recommend watching it to see more.
Where do I think we can go from here
I haven’t tried state machine drawings yet, and I think that can help extend what we can do with drawings.
I also would love to explore what other people have done as helpful aids for the drawings to get GPT4-Vision to come up with something better.
The 7 GUIs is a benchmark for comparing different GUI frameworks, proposed by Eugen Kiss. See his explanation below.
There are countless GUI toolkits in different languages and with diverse approaches to GUI development.
Yet, diligent comparisons between them are rare.
Whereas in a traditional benchmark competing implementations are compared in terms of their resource consumption,
here implementations are compared in terms of their notation.
To that end, 7GUIs defines seven tasks that represent typical challenges in GUI programming.
In addition, 7GUIs provides a recommended set of evaluation dimensions.
— Eugen Kiss
I’m going to walkthrough each GUI using Svelte, and annotate the code.
Counter
The task is to build a frame containing a label or read-only textfield T and a button B.
Initially, the value in T is “0” and each click of B increases the value in T by one.
<script> // Initialize the counter with "0" (as it says in the spec) let count = 0;</script><!-- Display the count, as a number input --><input type="number" bind:value={count}/><!-- Add a button that will increment the counter by 1 with each click. --><button on:click={() => (count += 1)}>count</button>
Temperature
The task is to build a frame containing two textfields TC and
TF representing the temperature in Celsius and Fahrenheit,
respectively. Initially, both TC and TF are empty. When
the user enters a numerical value into TC the corresponding value
in TF is automatically updated and vice versa. When the user enters
a non-numerical string into TC the value in TF is not
updated and vice versa. The formula for converting a temperature C in Celsius
into a temperature F in Fahrenheit is C = (F - 32) * (5/9) and the dual
direction is F = C * (9/5) + 32.
<script lang="ts"> // Initialize the values of celsius and fahrenheit let c = 20; let f = 68; // Given the value from Celsius, update Fahrenheit function setBothFromC(value: number): void { // The + is to convert the string to a number c = +value; // Use the formula from the spec to update fahrenheit f = +(32 + (9 / 5) * c).toFixed(1); } // Given the value from Fahrenheit, update Celsius function setBothFromF(value: number): void { f = +value; // Use the formula from the spec to update celsius c = +((5 / 9) * (f - 32)).toFixed(1); }</script><!--Add two different inputs. Since the inputs are two-way bound by the values,`c` and `f`, we can add an event listener to run the function to convert theother value.--><input value={c} on:input={(e) => setBothFromC(e.target.value)} type="number"/>°C =<input value={f} on:input={(e) => setBothFromF(e.target.value)} type="number"/>°F
Flight Booker
The task is to build a frame containing a form with three textfields
The task is to build a frame containing a combobox C with the two options
“one-way flight” and “return flight”, two textfields T1 and
T2 representing the start and return date, respectively, and a
button B for submitting the selected flight. T2 is enabled iff C’s
value is “return flight”. When C has the value “return flight” and
T2’s date is strictly before T1’s then B is disabled.
When a non-disabled textfield T has an ill-formatted date then T is colored
red and B is disabled. When clicking B a message is displayed informing the
user of his selection (e.g. “You have booked a one-way flight on
04.04.2014.”). Initially, C has the value “one-way flight” and T1
as well as T2 have the same (arbitrary) date (it is implied that
T2 is disabled).
<script> const DAY_IN_MS = 86400000 const tomorrow = new Date(Date.now() + DAY_IN_MS); // Create an array of year, month, day, in this format: YYYY-MM-DD let start = [ tomorrow.getFullYear(), pad(tomorrow.getMonth() + 1, 2), pad(tomorrow.getDate(), 2), ].join("-"); // our reactive variables let end = start; let isReturn = false; // Running statements reactively, updating the variables when they are changed $: startDate = convertToDate(start); $: endDate = convertToDate(end); // Click handler for the button function bookFlight() { // Determine type of return const type = isReturn ? "return" : "one-way"; let message = `You have booked a ${type} flight, leaving ${startDate.toDateString()}`; if (type === "return") { message += ` and returning ${endDate.toDateString()}`; } alert(message); } // Convert a string in the format YYYY-MM-DD to a Date object function convertToDate(str) { const split = str.split("-"); return new Date(+split[0], +split[1] - 1, +split[2]); } // Pad a number with leading zeros function pad(x, len) { x = String(x); while (x.length < len) x = `0${x}`; return x; }</script><!-- Create your select input for one-way or return flight option --><select bind:value={isReturn}> <option value={false}>one-way flight</option> <option value={true}>return flight</option></select><!-- Bind the inputs --><input type="date" bind:value={start} /><input type="date" bind:value={end} disabled={!isReturn} /><!-- Attempt to book flight --><button on:click={bookFlight} disabled={isReturn && startDate >= endDate} >book</button>
Timer
The task is to build a frame containing a gauge G for the elapsed time e, a
label which shows the elapsed time as a numerical value, a slider S by which
the duration d of the timer can be adjusted while the timer is running and a
reset button R. Adjusting S must immediately reflect on d and not only when S
is released. It follows that while moving S the filled amount of G will
(usually) change immediately. When e ≥ d is true then the timer stops (and G
will be full). If, thereafter, d is increased such that d > e will be true
then the timer restarts to tick until e ≥ d is true again. Clicking R will
reset e to zero.
<script> // It turns out, you can't run this in Astro without saying this component is client only import { onDestroy } from "svelte"; // Start elapsed at 0 milliseconds let elapsed = 0; // Set the range input to be 5 seconds / 5000 milliseconds let duration = 5000; let last_time = window.performance.now(); let frame; // IIFE for animation loop (function update() { // pass in the function for the animation frame (infinite looping) frame = requestAnimationFrame(update); // performance.now() is like Date.now(), but more accurate to tenths of a milliseconds const time = window.performance.now(); // Take the minimum of the time elapsed and add it to the new elapsed time elapsed += Math.min(time - last_time, duration - elapsed); last_time = time; })(); // When the component is destroyed, cancel the animation frame onDestroy(() => { cancelAnimationFrame(frame); });</script><!-- Create the label and use the progress tag to show the time elapsed vs duration --><label> elapsed time: <progress value={elapsed / duration} /></label><div>{(elapsed / 1000).toFixed(1)}s</div><label> duration: <!-- Bind the input to the duration. Max 20 seconds --> <input type="range" bind:value={duration} min="1" max="20000" /></label><!-- Allow the user to reset the timer --><button on:click={() => (elapsed = 0)}>reset</button>
CRUD
The task is to build a frame containing the following elements: a textfield
Tprefix, a pair of textfields Tname and
Tsurname, a listbox L, buttons BC, BU and
BD and the three labels as seen in the screenshot. L presents a
view of the data in the database that consists of a list of names. At most one
entry can be selected in L at a time. By entering a string into
Tprefix the user can filter the names whose surname start with the
entered prefix—this should happen immediately without having to submit the
prefix with enter. Clicking BC will append the resulting name from
concatenating the strings in Tname and Tsurname to L.
BU and BD are enabled if an entry in L is selected. In
contrast to BC, BU will not append the resulting name
but instead replace the selected entry with the new name. BD will
remove the selected entry. The layout is to be done like suggested in the
screenshot. In particular, L must occupy all the remaining space.
<script> // Have some people to start with let people = [ { first: "Hans", last: "Emil" }, { first: "Max", last: "Mustermann" }, { first: "Roman", last: "Tisch" }, ]; // Initialize the bound variables let prefix = ""; let first = ""; let last = ""; // Initialize the selected item index let i = 0; // Reactive statements when the changes $: filteredPeople = prefix ? people.filter((person) => { const name = `${person.last}, ${person.first}`; // Filter based off first or last name return name.toLowerCase().startsWith(prefix.toLowerCase()); }) : people; // Reactively change the selected when filtered people $: selected = filteredPeople[i]; // Reset all inputs when new selection made $: reset_inputs(selected); // Create a new person function create() { people = people.concat({ first, last }); i = people.length - 1; first = last = ""; } // Update the selected person function update() { selected.first = first; selected.last = last; people = people; } // Remove the selected person function remove() { // Remove selected person from the source array (people), not the filtered array const index = people.indexOf(selected); people = [...people.slice(0, index), ...people.slice(index + 1)]; first = last = ""; i = Math.min(i, filteredPeople.length - 2); } // Reset the input for first and last names function reset_inputs(person) { first = person ? person.first : ""; last = person ? person.last : ""; }</script><input placeholder="filter prefix" bind:value={prefix}/><select bind:value={i} size={5}> <!-- Loop through the filtered people --> {#each filteredPeople as person, i} <option value={i}>{person.last}, {person.first}</option> {/each}</select><!-- Create inputs for first and last names --><label ><input bind:value={first} placeholder="first" /></label><label ><input bind:value={last} placeholder="last" /></label><!-- CRUD operators --><div class="buttons"> <button on:click={create} disabled={!first || !last}>create</button> <button on:click={update} disabled={!first || !last || !selected} >update</button > <button on:click={remove} disabled={!selected}>delete</button></div>
Circle Drawer
The task is to build a frame containing an undo and redo button as well as a
canvas area underneath. Left-clicking inside an empty area inside the canvas
will create an unfilled circle with a fixed diameter whose center is the
left-clicked point. The circle nearest to the mouse pointer such that the
distance from its center to the pointer is less than its radius, if it exists,
is filled with the color gray. The gray circle is the selected circle C.
Right-clicking C will make a popup menu appear with one entry “Adjust
diameter…”. Clicking on this entry will open another frame with a slider
inside that adjusts the diameter of C. Changes are applied immediately.
Closing this frame will mark the last diameter as significant for the
undo/redo history. Clicking undo will undo the last significant change (i.e.
circle creation or diameter adjustment). Clicking redo will reapply the last
undoed change unless new changes were made by the user in the meantime.
<script> // Initialize the bound variables let i = 0; let undoStack = [[]]; let circles = []; let selected; let adjusting = false; let adjusted = false; // On handling click, create circle with default radius 50 px function handleClick(event) { if (adjusting) { adjusting = false; // if circle was adjusted, // push to the stack if (adjusted) push(); return; } const circle = { cx: event.clientX, cy: event.clientY, r: 50, }; // Add circles to list of circles. The selected circle is the current circle circles = circles.concat(circle); selected = circle; push(); } function adjust(event) { selected.r = +event.target.value; circles = circles; adjusted = true; } function select(circle, event) { if (!adjusting) { event.stopPropagation(); selected = circle; } } // Use a stack for keeping track of the circles function push() { const newUndoStack = undoStack.slice(0, ++i); newUndoStack.push(clone(circles)); undoStack = newUndoStack; } function travel(d) { circles = clone(undoStack[(i += d)]); adjusting = false; } function clone(circles) { return circles.map(({ cx, cy, r }) => ({ cx, cy, r })); }</script><!-- Put in the buttons for controls --><div class="controls"> <button on:click={() => travel(-1)} disabled={i === 0}>undo</button> <button on:click={() => travel(+1)} disabled={i === undoStack.length - 1} >redo</button ></div><!-- Draw with an SVG. Bind the click handler --><!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions --><svg on:click={handleClick}> <!-- Draw all of the circles --> {#each circles as circle} <!-- svelte-ignore a11y-click-events-have-key-events --> <circle cx={circle.cx} cy={circle.cy} r={circle.r} on:click={(event) => select(circle, event)} on:contextmenu|stopPropagation|preventDefault={() => { // When right-clicking, open the adjuster adjusting = !adjusting; if (adjusting) selected = circle; }} fill={circle === selected ? "#ccc" : "white"} /> {/each}</svg><!-- Show the adjuster if adjusting a circle's size -->{#if adjusting} <div class="adjuster"> <p>adjust diameter of circle at {selected.cx}, {selected.cy}</p> <input type="range" value={selected.r} on:input={adjust} /> </div>{/if}
Cells
The task is to create a simple but usable spreadsheet application. The
spreadsheet should be scrollable. The rows should be numbered from 0 to 99 and
the columns from A to Z. Double-clicking a cell C lets the user change C’s
formula. After having finished editing the formula is parsed and evaluated and
its updated value is shown in C. In addition, all cells which depend on C must
be reevaluated. This process repeats until there are no more changes in the
values of any cell (change propagation). Note that one should not just
recompute the value of every cell but only of those cells that depend on
another cell’s changed value. If there is an already provided spreadsheet
widget it should not be used. Instead, another similar widget (like JTable in
Swing) should be customized to become a reusable spreadsheet widget.
This one isn’t in the Svelte documentation, so I found a different
implementation that went through it perfectly. Link
Cells is split up into two Svelte components: Cell and Cells.
<!-- Cell.svelte --><script> // Initialized props export let j; export let i; export let focused; export let data; export let p; export let handleFocus; export let handleBlur; export let handleKeydown; export let handleInput; // Keep track of the current key let key = j + i; // Keep track if a cell is focused let hasFocus = false; $: if (focused === key && !hasFocus) { hasFocus = true; } else if (focused !== key && hasFocus) { hasFocus = false; }</script><!-- When focused, change the cell into an input --><!-- Otherwise parse the formula -->{#if hasFocus} <input id={"input-" + key} value={$data[key] || ""} autofocus on:focus={() => handleFocus(key)} on:blur={() => handleBlur(key)} on:keydown={(e) => handleKeydown(e, j, i)} on:input={(e) => handleInput(e, key)} />{:else} <div>{p.parse($data[key]) || ""}</div>{/if}
<!-- Cells.svelte --><script> import Cell from "./Cell.svelte"; import { data } from "./store.js"; import { sampleData } from "./sampleData.js"; import { Parser } from "./parse.js"; // Initialize with the sample data set data.set(sampleData); // Create 26 columns w/ the letters of the alphabet const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // Max 100 x 100 cells export let shape = [100, 100]; const rows = range(shape[1]); const columns = letterRange(shape[0]); const p = new Parser(data, columns, rows); let focused; let tBody; // Create range array function range(n) { return [...Array(n).keys()]; } // Create letter range function letterRange(n) { return range(n).map(getNumberAsLetters); } // Loop through letters function getBase26(n) { let result = []; while (n > 25) { let remainder = n % 26; result.push(remainder); n = Math.floor(n / 26) - 1; } result.push(n); return result.reverse(); } // Get the letter range and join them function getNumberAsLetters(n) { let arr = getBase26(n); return arr.map((num) => LETTERS[num]).join(""); } function handleFocus(key) { if (focused !== key) { $data[key] = $data[key] || ""; focused = key; setTimeout(() => { let target = tBody.querySelector("#input-" + key); if (target) { target.focus(); target.setSelectionRange(0, 9999); } }, 10); } } function handleBlur(key) { if (focused === key) focused = undefined; } function handleInput(e, key) { $data[key] = e.target.value; } function handleKeydown(e, column, row) { // Navigate across the spreadsheet with arrow keys (and alt/option key) let selector; if (e.key === "ArrowUp") { let newRow = findAdjacent(rows, row, "before"); selector = newRow !== null ? column + newRow : null; } if (e.key === "ArrowDown" || e.key === "Enter") { let newRow = findAdjacent(rows, row, "after"); selector = newRow !== null ? column + newRow : null; } if (e.key === "ArrowLeft" && e.altKey) { let newColumn = findAdjacent(columns, column, "before"); selector = newColumn !== null ? newColumn + row : null; } if (e.key === "ArrowRight" && e.altKey) { let newColumn = findAdjacent(columns, column, "after"); selector = newColumn !== null ? newColumn + row : null; } if (selector) { e.preventDefault(); handleFocus(selector); } } function findAdjacent(arr, value, direction) { let index = arr.indexOf(value); if (index === -1) return null; if (direction === "before") return arr[index - 1] === undefined ? null : arr[index - 1]; if (direction === "after") return arr[index + 1] || null; return null; } function clear() { data.set({}); }</script><div class="wrapper"> <table> <thead> <tr> <td class="row-key" /> {#each columns as column} <td class="column-key">{column}</td> {/each} </tr> </thead> <tbody bind:this={tBody}> {#each rows as i} <tr id={"row-" + i}> <td class="row-key">{i}</td> {#each columns as j} <td id={j + i} on:click={() => handleFocus(j + i)}> <Cell {j} {i} {focused} {data} {p} {handleFocus} {handleBlur} {handleKeydown} {handleInput} /> </td> {/each} </tr> {/each} </tbody> </table></div><button on:click={clear}>Clear</button>
There are two utility functions to help out the operations: parse and store
(the latter being the Svelte store to save in local state).
// parse.jsexport class Parser { constructor(store, columns, rows) { this.cells = {} this.store = store this.columns = columns this.rows = rows this.operations = { sum: (a, b) => a + b, sub: (a, b) => a - b, mul: (a, b) => a * b, div: (a, b) => a / b, mod: (a, b) => a % b, exp: (a, b) => a ** b } // subscribe to store this.store.subscribe(value => { this.cells = value }) } cartesianProduct(letters, numbers) { var result = [] letters.forEach(letter => { numbers.forEach(number => { result.push(letter + number) }) }) return result } findArrRange(arr, start, end) { let startI = arr.indexOf(start) let endI = arr.indexOf(end) if (startI == -1 || endI == -1 || startI > endI) return [] return arr.slice(startI, endI + 1) } getRange(rangeStart, rangeEnd) { rangeStart = this.splitOperand(rangeStart) rangeEnd = this.splitOperand(rangeEnd) let letters = this.findArrRange(this.columns, rangeStart[0], rangeEnd[0]) let numbers = this.findArrRange(this.rows, rangeStart[1], rangeEnd[1]) return this.cartesianProduct(letters, numbers) } splitOperand(operand) { return [operand.match(/[a-zA-Z]+/)[0], Number(operand.match(/\d+/)[0])] } rangeOperation(op, rangeStart, rangeEnd) { if (!(this.isWellFormed(rangeStart) && this.isWellFormed(rangeEnd))) return this.originalString let range = this.getRange(rangeStart, rangeEnd) return range .map(address => Number(this.parse(this.cells[address]))) .reduce(this.operations[op]) } singleOperation(op, operand1, operand2) { let first = this.parseOperand(operand1) let second = this.parseOperand(operand2) if (first === null || second === null) return this.originalString return this.operations[op](first, second).toString() } isWellFormed(operand) { return /[a-zA-Z]+\d+/.test(operand) } parseOperand(operand) { if (!isNaN(Number(operand))) return Number(operand) if (operand in this.cells) return Number(this.parse(this.cells[operand])) if (this.isWellFormed(operand)) return 0 return null } parseOperation(op, formula) { if (!(formula.startsWith('(') && formula.endsWith(')'))) return this.originalString formula = formula.slice(1, formula.length - 1) let operationType let formulaArr if (formula.includes(',')) { operationType = 'single' formulaArr = formula.split(',') } else if (formula.includes(':')) { operationType = 'range' formulaArr = formula.split(':') } if (formulaArr.length !== 2) return this.originalString if (operationType === 'single') return this.singleOperation(op, formulaArr[0], formulaArr[1]) if (operationType === 'range') return this.rangeOperation(op, formulaArr[0], formulaArr[1]) return this.originalString } parse(str) { this.originalString = str if (typeof str !== 'string') return '' if (!str.startsWith('=')) return str let formula = str.slice(1) if (formula.slice(0, 3).toLowerCase() in this.operations) { return this.parseOperation( formula.slice(0, 3).toLowerCase(), formula.slice(3).toUpperCase() ) } else { return this.cells[formula] || str } }}
// store.jsimport { writable } from "svelte/store";export const data = writable({});
The last file is to load prefilled data, but we don’t need to go over that.
// An example of the sampleData fileexport let sampleData = { A0: "Data", A1: "20", A2: "15"};
SolidJS is a Javascript framework for building fast, declarative UIs on the web. It shares many ideas with React, but does not use the virtual DOM to deliver a more performant and pragmatic developer experience.
In the playground, you can view the compiled output.
Also, you can change the compile mode, between “Client side rendering”, “Server side rendering”, and “Client side rendering with hydration”
Any code that you write in the playground can be exported to a coding sandbox, like Codesandbox. So helpful!
Philosophy - Think Solid
Declarative Data
Vanishing Components
Solid updates are completely independent of the components. Component functions are called once and then cease to exist.
Read/Write segregation
We don’t need true immutability to enforce unidirectional flow, just the ability to make the conscious decision which consumers may write and which may not.
Simple is better than easy
Compilation
Solid’s JSX compiler doesn’t just compile JSX to JavaScript; it also extracts reactive values (which we’ll get to later in the tutorial) and makes things more efficient along the way.
This is more involved than React’s JSX compiler, but much less involved than something like Svelte’s compiler. Solid’s compiler doesn’t touch your JavaScript, only your JSX.
Destructuring props is usually a bad idea in Solid. Under the hood, Solid uses proxies to hook into props objects to know when a prop is accessed. When we destructure our props object in the function signature, we immediately access the object’s properties and lose reactivity.
So in general, avoid the following:
function Bookshelf({ name }) { return ( <div> <h1>{name}'s Bookshelf</h1> <Books /> <AddBook /> </div> );}
And replace with props instead.
Dependency Arrays
In React, you’d declare the dependencies explicitly using the dependency array. If you didn’t, the effect would rerun whenever any state in the component changes. In Solid, dependencies are tracked automatically, and you don’t have to worry about extra reruns.
Looping with array.map
If we used array.map in Solid, every element inside the book would have to rerender whenever the books signal changes. The For component checks the array when it changes, and only updates the necessary element. It’s the same kind of checking that React’s VDOM rendering system does for us when we use .map.
Conditional if statements on re-rendering
In the Building UI with Components section of this tutorial, we noted that component functions run only once in Solid. This means the JSX returned from that initial function return is the only JSX that will ever be returned from the function.
In Solid, if we want to conditionally display JSX in a component, we need that condition to reside within the returned JSX. While this takes some adjustment when coming from React, we have found that the fine-grained control afforded by Solid’s reactive system is worth the trade-off.
Reactivity and proxy objects
In Solid, props and stores are proxy objects that rely on property access for tracking and reactive updates. Watch out for destructuring or early property access, which can cause these properties to lose reactivity or trigger at the wrong time.
onChange vs. onInput
In React, onChange fires whenever an input field is modified, but this isn’t how onChangeworks natively. In Solid, use onInput to subscribe to each value change.
No VDOM or VDOM APIs
Finally, there is no VDOM so imperative VDOM APIs like React.Children and React.cloneElement have no equivalent in Solid. Instead of creating or modifying DOM elements directly, express your intentions declaratively.
Solid Primitives
Signals - The basic way to manage state in the application
Similar to useState in React, but as a reactive value
Derived state - you can track a computed state from a signal, which is also reactive
You can pass the signal as a prop like this: <BookList books={books()} />. It’s not a typo to use the function as it’s passing an accessor, which is important for reactivity.
Effects - ability to react to signal changes
A driving philosophy of Solid is that, by treating everything as a signal or an effect, we can better reason about our application.
Looping
Solid has a component called <For /> with an each prop. You can pass in a signal that will make this reactive.
As you can see, onInput is the event handler that takes in the event. In this case, we are setting the new book for each input (the title and author).
The onClick handler for the button uses the addBook function where it can prevent the form from submitting, set the books using a new array, then resetting the new book. It should be noted that setBooks is using a callback function where you access the current state. Also, it should be noted not to mutate state by creating that new array (much like in Redux practice).
The primitive for any external data source is createResource. The function returns a deconstructed array with the data. It takes two arguments: the signal and the data fetching function.
Putting it all together, query is the signal. searchBooks is the data fetching function. Once the data is returned, we can loop over it, and for each item, we can set the books if selected.
The following is a code example introducing how Reactivity or Reactive Programming works.
import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(2);const [multipler, setMultiplier] = createSignal(2);const product = () => count() * multipler();// Change the count every secondsetInterval(() => { setCount(count() + 1);}, 1000);// Change the multiplier every 2.5 secondssetInterval(() => { setCount(multipler() + 1);}, 2500);// Effect automatically detects when a signal has changed// So you don't have to add a dependency array.// This is defined as "reactivity"createEffect(() => { console.log(`${count()} * ${multiplier()} = ${product()}`);});
createSignal works by creating a data structure that can read and write. To each of these functions, subscribers are added and updated
CreateEffect works by executing on a context queue (JS array). It takes its queue, executes it, then pops it (at least in pseudocode)
SolidJS uses “granular updates” so only the variables that change only update the DOM, and not entire components.
In this example, we extracted Multipler into its own component, added props, like React, and called it multiple times in App. As you will also notice, signals do not have to be in the function scope! This is counter to what you do in React. Of course, if you don’t want to share the signal across other components, you can keep it in the functional lexical scope.
I started this journey into Typescript by taking a ton of notes with Github Copilot X, “Typescript in 50 Lessons”, and the official documentation. I was able to tailor my experience in understanding how to create types through this method. Without further ado, here’s some notes that I generated (and modified).
What is Typescript?
TypeScript is a superset of JavaScript that adds optional static typing and other features to the language. It is designed to make it easier to write and maintain large-scale JavaScript applications. TypeScript code is compiled into JavaScript code that can run in any browser or JavaScript runtime.
TypeScript provides features such as classes, interfaces, enums, and modules that are not available in standard JavaScript. It also includes support for modern JavaScript features such as async/await and decorators.
TypeScript is developed and maintained by Microsoft, and it is open source and free to use. It is widely used in web development, and many popular frameworks and libraries such as Angular, React, and Vue have TypeScript support.
Static Typing
Static typing in TypeScript allows you to specify the types of variables, function parameters, and return values. This means that you can catch type-related errors at compile-time rather than at runtime.
For example, you can specify that a variable is of type string, and TypeScript will give you an error if you try to assign a number to that variable. Similarly, you can specify that a function takes a parameter of type number, and TypeScript will give you an error if you try to call that function with a string.
Here’s an example of a function that takes two numbers and returns their sum, with static typing:
function addNumbers(x: number, y: number): number { return x + y;}
In this example, the x and y parameters are of type number, and the function returns a value of type number. If you try to call this function with non-numeric arguments, TypeScript will give you an error.
In addition, you can declare types for functions separately to the function implementation. This allows you to define a function type once and reuse it in multiple places.
The void type is used to indicate that a function does not return a value. You can use void as the return type of a function to explicitly indicate that the function does not return anything.
Here’s an example of a function that returns void:
function logMessage(message: string): void { console.log(message);}
In this example, the logMessage function takes a parameter of type string and logs it to the console. The function returns void, which means it does not return a value.
Type Assertions
A type assertion is a way to tell the compiler that you know more about the type of a value than it does. Type assertions are sometimes necessary when working with values that have a more general type than you need.
You can use a type assertion by adding the type you want to assert to the end of an expression, preceded by the as keyword. Here’s an example:
let myValue: any = "hello world";let myLength: number = (myValue as string).length;
In this example, the myValue variable is declared as type any, which means it can hold any value. We then use a type assertion to tell the compiler that we know myValue is actually a string, so we can access its length property.
You can also use angle bracket syntax (< >) to perform a type assertion:
let myValue: any = "hello world";let myLength: number = (<string>myValue).length;
In this example, the type assertion is performed using angle bracket syntax instead of the as keyword.
It’s important to note that type assertions do not change the runtime behavior of your code. They only affect the type checking performed by the TypeScript compiler.
You can use rest parameters to represent an indefinite number of arguments as an array. Rest parameters are denoted by an ellipsis (…) followed by the parameter name.
Here’s an example of a function that uses rest parameters:
function sum(...numbers: number[]): number { return numbers.reduce((total, num) => total + num, 0);}
Primitive Types
There are several primitive types that represent the most basic types of values. These primitive types include:
number: represents numeric values, including integers and floating-point numbers.
string: represents textual values, such as words and sentences.
boolean: represents logical values, either true or false.
null: represents the intentional absence of any object value.
undefined: represents the unintentional absence of any object value.
symbol: represents a unique identifier that can be used as an object property.
let age: number = 30;let name: string = "John";let isStudent: boolean = true;let favoriteColor: string | null = null;let phoneNumber: string | undefined = undefined;let id: symbol = Symbol("id");
Non-primitive types are types that are based on objects rather than simple values. These types include:
object: represents any non-primitive value, including arrays, functions, and objects.
array: represents an ordered list of values of a single type.
function: represents a callable object that can be invoked with arguments.
class: represents a blueprint for creating objects that have properties and methods.
interface: represents a contract that describes the shape of an object.
enum: represents a set of named constants.
Dynamically Generated Types
Dynamically generated types are types that are generated at runtime based on the shape of the data. There are several ways to generate types dynamically in TypeScript:
Index signatures: You can use index signatures to define an object type with dynamic keys. For example:
interface Dictionary<T> { [key: string]: T;}
This interface defines a dictionary type with a dynamic key of type string and a value of type T.
Type assertions: You can use type assertions to cast a value to a specific type at runtime.
const data = JSON.parse(jsonString) as MyType;
This code uses a type assertion to cast the parsed JSON data to a specific type called MyType.
Type guards: You can use type guards to check the type of a value at runtime and conditionally cast it to a specific type. For example:
function isPerson(obj: any): obj is Person { return "name" in obj && "age" in obj;}function printPerson(obj: any) { if (isPerson(obj)) { console.log(`Name: ${obj.name}, Age: ${obj.age}`); } else { console.log("Not a person"); }}
Intersection types: You can use intersection types to combine multiple types into one.
Union Types
A union type is a type that can represent values of multiple types. Union types are denoted by the | symbol between the types.
function printId(id: number | string) { console.log(`ID is ${id}`);}
Union types are useful when you want to write code that can handle multiple types of values.
Intersection Types
An intersection type is a type that combines multiple types into one. Intersection types are denoted by the & symbol between the types.
interface Named { name: string;}interface Loggable { log(): void;}function logName(obj: Named & Loggable) { console.log(`Name is ${obj.name}`); obj.log();}
Intersection types are useful when you want to write code that can handle objects with multiple sets of properties and methods.
Value Types
This interface definition ensures that this Event must have a kind property with one of the three specified values. This can help prevent errors and make the code more self-documenting.
You can use a type guard function to check the type of a value at runtime and conditionally cast it to a specific type. A type guard is a function that returns a boolean value and has a special obj is Type syntax that tells TypeScript that the value is of a specific type.
function isPerson(obj: any): obj is Person { return "name" in obj && "age" in obj;}
You can then use the type guard function to conditionally cast a value to a specific type. Here’s an example:
function printPerson(obj: any) { if (isPerson(obj)) { console.log(`Name: ${obj.name}, Age: ${obj.age}`); } else { console.log("Not a person"); }}
Type Assertions
Type assertions in TypeScript are a way to tell the compiler that you know more about the type of a value than it does. Type assertions are similar to type casting in other languages, but they don’t actually change the type of the value at runtime. Type assertions use the as keyword.
const myValue: any = "hello world";const myLength: number = (myValue as string).length;
This can be useful in JSX format too.
const myComponent = ( <MyComponent prop1={value1} prop2={value2} />) as JSX.Element;
Type Aliases
Type aliases in TypeScript are a way to create a new name for an existing type. They allow you to define a custom name for a type that may be complex or difficult to remember.
Type aliases can also be used with union types, intersection types, and other advanced type constructs. They can help make your code more readable and maintainable by giving complex types a simpler name.
Mapped Types
Mapped types in TypeScript are a way to create new types based on an existing type by transforming each property in the original type in a consistent way. Mapped types use the keyof keyword to iterate over the keys of an object type and apply a transformation to each key.
type Readonly<T> = { readonly [P in keyof T]: T[P];};interface Person { name: string; age: number;}const myPerson: Readonly<Person> = { name: 'John', age: 30 };myPerson.name = 'Jane'; // Error: Cannot assign to 'name' because it is a read-only property.
Mapped types can be used to create many other types, such as Partial, Pick, and Record. They are a powerful feature of TypeScript that can help make your code more expressive and maintainable.
Partial is a mapped type that creates a new type with all properties of the original type set to optional. Here’s an example:
A type predicate in TypeScript is a function that takes an argument and returns a boolean value indicating whether the argument is of a certain type. Type predicates are used to narrow the type of a variable or parameter based on a runtime check.
function isString(value: unknown): value is string { return typeof value === 'string';}function myFunc(value: unknown) { if (isString(value)) { // value is now of type string console.log(value.toUpperCase()); } else { console.log('Not a string'); }}
We define a function myFunc that takes an argument value of type unknown. We then use the isString function to check if value is of type string. If it is, we can safely call the toUpperCase method on value because TypeScript has narrowed the type to string.
never, undefined, null Types
never is a type that represents a value that will never occur. It is used to indicate that a function will not return normally, or that a variable will never have a certain value.
function throwError(message: string): never { throw new Error(message);}
undefined and null are both types and values. The undefined type represents a value that is not defined, while the null type represents a value that is explicitly set to null.
TypeScript also has a --strictNullChecks compiler option that can help prevent null and undefined errors. When this option is enabled, variables that are not explicitly set to null or undefined are considered to be of a non-nullable type. This means that you cannot assign null or undefined to these variables without first checking for their existence.
Classes
Classes in TypeScript are a way to define object-oriented programming (OOP) constructs. They allow you to define a blueprint for creating objects that have properties and methods.
Here’s an example of a class in TypeScript:
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log( `Hello, my name is ${this.name} and I am ${this.age} years old.` ); }}
In this example, the Person class has two properties (name and age) and a method (sayHello). The constructor method is used to initialize the properties when a new object is created.
You can create a new Person object like this:
const person = new Person("Alice", 30);person.sayHello(); // logs "Hello, my name is Alice and I am 30 years old."
One of the main differences between TypeScript and JavaScript classes is that TypeScript allows you to specify the types of class properties, method parameters, and return values. This helps catch type-related errors at compile-time rather than at runtime.
Another difference is that TypeScript provides access modifiers such as public, private, and protected, which allow you to control the visibility of class members. This can help you write more secure and maintainable code.
Finally, TypeScript classes can implement interfaces, which are contracts that describe the shape of an object. This can help enforce type checking on objects that implement the interface.
Access modifiers in TypeScript classes are keywords that determine the visibility of class members (properties and methods). There are three access modifiers in TypeScript:
public: Public members are accessible from anywhere, both inside and outside the class.
private: Private members are only accessible from within the class. They cannot be accessed from outside the class, not even from derived classes.
protected: Protected members are accessible from within the class and from derived classes. They cannot be accessed from outside the class hierarchy.
Here’s an example of a class with access modifiers:
class Person { public name: string; private age: number; protected address: string; constructor(name: string, age: number, address: string) { this.name = name; this.age = age; this.address = address; } public sayHello() { console.log( `Hello, my name is ${this.name} and I am ${this.age} years old.` ); } private getAge() { return this.age; } protected getAddress() { return this.address; }}
In this example, the name property is public, so it can be accessed from anywhere. The age property is private, so it can only be accessed from within the Person class. The address property is protected, so it can be accessed from within the Person class and from derived classes.
Abstract Class
An abstract class is a class that cannot be instantiated directly. Instead, it is meant to be subclassed by other classes that provide concrete implementations of its abstract methods.
An abstract class can have both abstract and non-abstract methods. Abstract methods are declared without an implementation, and must be implemented by any concrete subclass. Non-abstract methods can have an implementation, and can be called by concrete subclasses.
Here’s an example of an abstract class in TypeScript:
abstract class Animal { abstract makeSound(): void; move(distanceInMeters: number) { console.log(`Animal moved ${distanceInMeters}m.`); }}class Dog extends Animal { makeSound() { console.log("Woof! Woof!"); }}const dog = new Dog();dog.makeSound(); // logs "Woof! Woof!"dog.move(10); // logs "Animal moved 10m."
In this example, the Animal class is an abstract class that defines an abstract makeSound method and a non-abstract move method. The Dog class is a concrete subclass of Animal that provides an implementation of the makeSound method. The Dog class can also call the move method inherited from Animal.
But then, what’s the difference between an abstract class and an interface? Take a look at the following example:
In this example, the Animal abstract class and the IAnimal interface both describe objects with a makeSound method and a move method. However, the Animal class is meant to be subclassed, while the IAnimal interface is meant to be implemented.
Some advantages of using inheritance instead of interfaces are:
Multiple Inheritance: An interface can be implemented by multiple classes, while an abstract class can only be subclassed by one class. This can help you create more flexible and reusable code.
Lighter Weight: An interface is a lighter weight construct than an abstract class, since it does not have any implementation details. This can help you write more modular and composable code.
Interfaces
In TypeScript, interfaces are contracts that describe the shape of an object. They define a set of properties and methods that an object must have in order to be considered an implementation of the interface.
Here’s an example of an interface in TypeScript:
interface Person { name: string; age: number; sayHello(): void;}
In this example, the Person interface has two properties (name and age) and a method (sayHello). Any object that implements the Person interface must have these properties and method.
You can use an interface to enforce type checking on objects that implement it. For example, you can define a function that takes a Person object as a parameter:
function greet(person: Person) { person.sayHello();}
In this example, the greet function takes a Person object as a parameter. TypeScript will give you an error if you try to call this function with an object that does not implement the Person interface.
While interfaces and classes have similar ways in defining object types, here are some differences:
Implementation: A class can have both properties and methods, while an interface can only have properties and method signatures. A class is an implementation of an object, while an interface is just a description of an object.
Instantiation: A class can be instantiated to create objects, while an interface cannot. An interface is just a contract that describes the shape of an object.
Inheritance: A class can inherit from another class or multiple classes, while an interface can only extend other interfaces.
Access Modifiers: A class can have access modifiers (public, private, protected) to control the visibility of its members, while an interface cannot.
The benefits of using an interface are the following:
Type checking: Interfaces allow you to enforce type checking on objects that implement them. This can help catch errors at compile-time rather than at runtime.
Code reuse: Interfaces can be used to define a common set of properties and methods that multiple objects can implement. This can help reduce code duplication and make your code more modular.
Abstraction: Interfaces can be used to abstract away implementation details and focus on the contract between objects. This can help make your code more maintainable and easier to reason about.
Polymorphism: Interfaces can be used to create polymorphic behavior, where different objects can be used interchangeably as long as they implement the same interface.
Polymorphism
Polymorphism is the ability of objects to take on multiple forms. In TypeScript, interfaces can be used to create polymorphic behavior, where different objects can be used interchangeably as long as they implement the same interface.
Here’s an example of polymorphism in TypeScript:
interface Shape { getArea(): number;}class Rectangle implements Shape { constructor(private width: number, private height: number) {} getArea() { return this.width * this.height; }}class Circle implements Shape { constructor(private radius: number) {} getArea() { return Math.PI * this.radius ** 2; }}function printArea(shape: Shape) { console.log(`The area of the shape is ${shape.getArea()}`);}const rectangle = new Rectangle(10, 20);const circle = new Circle(5);printArea(rectangle); // logs "The area of the shape is 200"printArea(circle); // logs "The area of the shape is 78.53981633974483"
In this example, the Shape interface defines a getArea method that returns a number. The Rectangle and Circle classes both implement the Shape interface, so they both have a getArea method. The printArea function takes a Shape object as a parameter, so it can be called with either a Rectangle or a Circle object. This is an example of polymorphism, where different objects can be used interchangeably as long as they implement the same interface.
Namespace
In TypeScript, a namespace is a way to group related code into a named scope. Namespaces can contain classes, interfaces, functions, and other code constructs.
You can define a namespace using the namespace keyword, and you can access its contents using the dot notation. Here’s an example:
In this example, the MyNamespace namespace contains an interface Person and a function greet. The Person interface is exported so that it can be used outside of the namespace. The greet function is also exported, and it takes a Person object as an argument.
To use the Person interface and the greet function, you can access them using the dot notation with the namespace name (MyNamespace.Person and MyNamespace.greet).
Enums
In TypeScript, an enum is a way to define a set of named constants. Enums are useful when you have a fixed set of values that a variable can take on, such as the days of the week or the colors of the rainbow.
Here’s an example of an enum in TypeScript:
enum Color { Red, Green, Blue,}let myColor: Color = Color.Red;console.log(myColor); // logs 0
In this example, the Color enum defines three named constants (Red, Green, and Blue). Each constant is assigned a numeric value (0, 1, and 2, respectively) by default. You can also assign string or numeric values explicitly:
enum Color { Red = "#ff0000", Green = "#00ff00", Blue = "#0000ff",}
Generics
Generics in TypeScript allow you to create reusable code components that can work with different types. They provide a way to define functions, classes, and interfaces that can work with a variety of data types, without having to write the same code multiple times.
Here’s an example of a generic function that takes an array of any type and returns the first element:
function getFirstElement<T>(arr: T[]): T { return arr[0];}
In this example, the syntax indicates that the function is generic and can work with any type. The T is a placeholder for the actual type that will be used when the function is called.
The arr parameter is an array of type T, and the function returns a value of type T. When the function is called, the actual type for T is inferred from the type of the array that is passed in.
Generics in TypeScript are commonly used in situations where you want to write code that can work with multiple types. Here are some common use cases for generics:
Collections: Generics can be used to create collections that can hold any type of data. For example, you can create a generic List class that can hold a list of any type of data.
Functions: Generics can be used to create functions that can work with any type of data. For example, you can create a generic identity function that returns its argument of type T.
Interfaces: Generics can be used to create interfaces that can work with any type of data. For example, you can create a generic Comparable interface that defines a method for comparing two objects of type T.
Classes: Generics can be used to create classes that can work with any type of data. For example, you can create a generic Repository class that provides CRUD operations for a data type T.
Modules
In TypeScript, a module is a way to organize code into reusable units. A module can contain classes, functions, interfaces, and other code constructs.
A module can be defined using the export keyword, which makes its contents available for use in other modules. You can also use the import keyword to import code from other modules.
Here’s an example of a module in TypeScript:
// math.tsexport function add(a: number, b: number): number { return a + b;}export function subtract(a: number, b: number): number { return a - b;}
In this example, the math.ts file defines a module that exports two functions (add and subtract). These functions can be imported and used in other modules:
In this example, the app.ts file imports the add and subtract functions from the math module using the import keyword.
Decorators
In TypeScript, a decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
Decorators can be used to modify the behavior of a class or its members, or to annotate them with additional metadata. For example, you can use a decorator to add logging or validation to a method, or to mark a property as required.
Here’s an example of a decorator in TypeScript:
function log(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${key} with arguments: ${JSON.stringify(args)}`); const result = originalMethod.apply(this, args); console.log(`Result of ${key}: ${JSON.stringify(result)}`); return result; }; return descriptor;}class Calculator { @log add(a: number, b: number) { return a + b; }}const calculator = new Calculator();console.log(calculator.add(1, 2)); // logs "Calling add with arguments: [1,2]" and "Result of add: 3"
In this example, the log function is a decorator that takes three arguments: the target object (the class prototype), the name of the decorated method (add), and a descriptor object that describes the method. The log function modifies the behavior of the method by adding logging statements before and after the original method is called.
The Calculator class has a method add that is decorated with the @log decorator. When the add method is called, the log decorator is executed, which logs the arguments and result of the method.
Similarities can occur on the class level. For example, in this following example, you can write a logger for instantiation.
function log(target: any) { console.log(`Creating instance of ${target.name}`);}@logclass Calculator { add(a: number, b: number) { return a + b; }}const calculator = new Calculator(); // logs "Creating instance of Calculator"console.log(calculator.add(1, 2)); // logs 3
I decided to re-write my personal website back in 2017, when I was much more active in writing on my blog. However, that changed quickly when I abandoned making updates in 2018 when I started my job at Clear Labs. There was no longer any time to write as we were working on their Clear Safety product.
I’ve had one or two failed attempts at doing a full re-write, and never fully committed to doing it until this past month. Part of the reason is I dreaded the migration from Jekyll. I knew most of my content was in markdown, but there was this fear in the back of my mind like it was an insurmountable task. Of course, that’s a fallacy, and when you know you could do other things with your time, this project inevitable went into my backlog.
Comeback with Astro
I’ve been itching to write again, and the urge trumped my fear of the migration. I decided to use Astro as my static site generator, with plans to run it as a server as a future. I wrote a small project in Astro, and thought how delightful it was, so I decided to see what the starter had for its blog starter kit. I was pleasantly surprised. It had a great base foundation to start migrating over blog posts, as long as I followed their markdown frontmatter, typically written as Yaml. By taking a look at their base schema, I could easily re-adapt the posts and get something rendering on the page write away.
import { defineCollection, z } from "astro:content";const blog = defineCollection({ // Type-check frontmatter using a schema schema: z.object({ title: z.string(), description: z.string(), // Transform string to Date object pubDate: z .string() .or(z.date()) .transform((val) => new Date(val)), updatedDate: z .string() .optional() .transform((str) => (str ? new Date(str) : undefined)), heroImage: z.string().optional(), }),});export const collections = { blog };
I needed additional metadata for my blog posts, like tags, post type, and a boolean for whether it was a draft.
I’ve created Next.js and Remix projects before, so the file-based routing system was intuitive. The preset included a blog folder with an index.astro and [[...slug]].astro file pre-filled. This made it super easy for my to figure out what was going on by reading the code and commenting the parts I didn’t understand.
After the blog posts migrated over, I quickly threw myself into the other pages that weren’t migrated over, specifically the about page, projects, logs, and newsletter series. Each of those became their own schemas. I updated their markdown frontmatter as well, and the personal site started to look like my old site again.
Deployment
The website was using Netlify, and while I personally would’ve preferred Vercel, it was a good choice. I setup some Github actions to continue to push to Github Pages as well. Netlify’s migration was a pain, because the configuration and its cache were set to build to Jekyll’s configuration. Migrating it over to Astro took some finagling to get the settings just right. I spent a good evening scratching my head until I found the correct environment variable to set it to.
A Side Note about Git
I created my old blog back in 2014, and I was still using master branch as the main branch. With this migration, I moved it over to main. For configuration reasons, it was having trouble moving over in my CI pipeline the first two or three builds. So please remember to check your settings.
Styling
At first, Astro gives you the css from the Bear Blog. While the minimalism was great, that’s not my type of styling. So I adapted it until it started the look the way I wanted it to. Quickly, I realized this isn’t going to be scalable. While astro limits their styling to the component you’re working on, it wasn’t good enough. And the global styles weren’t to my taste.
I knew I wanted to use Tailwind in a full project. Previously, I’ve only used it for tiny pet projects. I went full in immersion. At first, my global styles were being modified by the base.css injected styles from Tailwind. I put too much effort in trying to modify them when I realized, “I started from a base project, and I don’t need these base style files”. That’s when I stopped caring about my blog’s styling conventions and migrated over to Tailwind’s conventions.
Sometimes, it’s hard to break hard habits. The mantra of “Convention over configuration”, as heralded by Ruby on Rails”, was echoing in the back of my mind. While I am not fully migrated over to Tailwind yet, I plan to.
Conclusion
There’s a bunch of things left-over from this migration that I have to still work on.
Analytics (ideally, an alternative to Google Analytics)
Add filters for the writing so you can filter by tags
Migrate over my Newsletter from TinyLetter to Buttondown
Overall though, I’m happy with my switch, and plan on working on my website more. Astro makes it incredible easy to add components from other libraries that I’ve worked with in the past (personally used React, Vue, Svelte, and Solid), so I plan on making more interactive blog posts and projects for this website in the future.
If you have any questions about the migration, want to give me feedback, or would like some help on your own Astro blog, feel free to email me. I’m always happy to help. (Email is in the footer of all pages).
In Astro, there’s this concept of islands. You have a few options on making your page interactive.
From the Astro docs,
these are your options.
---// Example: hydrating framework components in the browser.import DemoApp from "../../components/DemoApp.svelte";---<!-- This component's JS will begin importing when the page loads --><DemoApp client:load /><!-- This component's JS will not be sent to the client untilthe user scrolls down and the component is visible on the page --><DemoApp client:visible /><!-- This component won't render on the server, but will render on the client when the page loads --><DemoApp client:only="svelte" />
I’ll go ahead and try each of these and see what happens.
The following was written for interns starting out with Javascript at Clear Labs.
Base Foundation
Whether this is your first time with Javascript or as a seasoned developer, you should have some base knowledge prior to working with React. While you can learn a framework, it’s more beneficial to understand the language it is written in. For example, what are promises and how does javascript handle asynchronous actions? What is the event loop? And how does Javascript fit in?
Here are some resources to get you started
Freecodecamp - if you have no foundational knowledge of Javascript or need a refresher for the Javascript syntax, start here
MDN Javascript - Mozilla’s documentation on where to get started with Javascript
MDN Promises - Mozilla’s documentation on promises
Async functions - Mozilla’s documentation on handling promises using async functions
Error Handling - Mozilla’s documentation about browser javascript errors
Going Deeper
Many developers find Javascript hard because it started as a scripting language, the syntax looks ugly, and you get these TypeErrors if you’re not careful. That said, with some major changes to the language since Node.js and Google’s V8 engine, Javascript has become a more seasoned programming language. You can develop classes, write generator functions, handle asynchronous events, and enumerate over lists much easier.
Once you’ve started with the basics above, feel free to continue to hone your skills with a deeper understanding of Javascript.
ES2015+ - a new set of functionality in Javascript that allows you to write more effective code. See the Ecmascript section below for more information.
You Don’t Know JS - A series of books written by Kyle Simpson that talks about diving deep into the core mechanisms of Javascript
Ecmascript
JavaScript is a subset of ECMAScript. JavaScript is basically ECMAScript at its core but builds upon it. Languages such as ActionScript, JavaScript, JScript all use ECMAScript as its core. As a comparison, AS/JS/JScript are 3 different cars, but they all use the same engine… each of their exteriors is different though, and there have been several modifications done to each to make it unique.
The history is, Brendan Eich created Mocha which became LiveScript, and later JavaScript. Netscape presented JavaScript to Ecma International, which develops standards and it was renamed to ECMA-262 aka ECMAScript.
It’s important to note that Brendan Eich’s “JavaScript” is not the same JavaScript that is a dialect of ECMAScript. He built the core language which was renamed to ECMAScript, which differs from the JavaScript which browser-vendors implement nowadays.
If your base understanding of Javascript is prior to ES6, you’ll want to read up on the basics. To start, arrow functions, classes, let and const statements are used throughout the app.
Arrow Functions
Often times we have nested functions in which we would like to preserve the context of this from its lexical scope. An example is shown below:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property 'name' of undefined });};
One common solution to this problem is to store the context of this using a variable:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { var that = this; // Store the context of this return arr.map(function (character) { return that.name + character; });};
We can also pass in the proper context of this:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; }, this);};
As well as bind the context:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map( function (character) { return this.name + character; }.bind(this) );};
Using Arrow Functions, the lexical value of this isn’t shadowed and we can re-write the above as shown:
function Person(name) { this.name = name;}Person.prototype.prefixName = function (arr) { return arr.map((character) => this.name + character);};
Best Practice: Use Arrow Functions whenever you need to preserve the lexical value of this.
Arrow Functions are also more concise when used in function expressions which simply return a value:
Best Practice: Use Arrow Functions in place of function expressions when possible.
Template Literals
Using Template Literals, we can now construct strings that have special characters in them without needing to escape them explicitly.
var text = 'This string contains "double quotes" which are escaped.';let text = `This string contains "double quotes" which don't need to be escaped anymore.`;
Template Literals also support interpolation, which makes the task of concatenating strings and values:
var name = "Tiger";var age = 13;console.log("My cat is named " + name + " and is " + age + " years old.");
Much simpler:
const name = "Tiger";const age = 13;console.log(`My cat is named ${name} and is ${age} years old.`);
In ES5, we handled new lines as follows:
var text = "cat\n" + "dog\n" + "nickelodeon";
Or:
var text = ["cat", "dog", "nickelodeon"].join("\n");
Template Literals will preserve new lines for us without having to explicitly place them in:
let text = `catdognickelodeon`;
Template Literals can accept expressions, as well:
let today = new Date();let text = `The time and date is ${today.toLocaleString()}`;
Classes
Prior to ES6, we implemented Classes by creating a constructor function and adding properties by extending the prototype:
class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); }}
Best Practice: While the syntax for creating classes in ES6 obscures how implementation and prototypes work under the hood, it is a good feature for beginners and allows us to write cleaner code.
Let / Const
Besides var, we now have access to two new identifiers for storing values —let and const. Unlike var, let and const statements are not hoisted to the top of their enclosing scope.
An example of using var:
var snack = "Meow Mix";function getFood(food) { if (food) { var snack = "Friskies"; return snack; } return snack;}getFood(false); // undefined
However, observe what happens when we replace var using let:
let snack = "Meow Mix";function getFood(food) { if (food) { let snack = "Friskies"; return snack; } return snack;}getFood(false); // 'Meow Mix'
This change in behavior highlights that we need to be careful when refactoring legacy code which uses var. Blindly replacing instances of var with let may lead to unexpected behavior.
Note: let and const are block scoped. Therefore, referencing block-scoped identifiers before they are defined will produce a ReferenceError.
console.log(x); // ReferenceError: x is not definedlet x = "hi";
Best Practice: Leave var declarations inside of legacy code to denote that it needs to be carefully refactored. When working on a new codebase, use let for variables that will change their value over time, and const for variables which cannot be reassigned.
Destructuring allows us to extract values from arrays and objects (even deeply nested) and store them in variables with a more convenient syntax.
Destructuring Arrays
var arr = [1, 2, 3, 4];var a = arr[0];var b = arr[1];var c = arr[2];var d = arr[3];let [a, b, c, d] = [1, 2, 3, 4];console.log(a); // 1console.log(b); // 2
Destructuring Objects
var luke = { occupation: "jedi", father: "anakin" };var occupation = luke.occupation; // 'jedi'var father = luke.father; // 'anakin'let luke = { occupation: "jedi", father: "anakin" };let { occupation, father } = luke;console.log(occupation); // 'jedi'console.log(father); // 'anakin'
Parameters
In ES5, we had varying ways to handle functions which needed default values, indefinite arguments, and named parameters. With ES6, we can accomplish all of this and more using more concise syntax.
Default Parameters
function addTwoNumbers(x, y) { x = x || 0; y = y || 0; return x + y;}
In ES6, we can simply supply default values for parameters in a function:
function addTwoNumbers(x = 0, y = 0) { return x + y;}addTwoNumbers(2, 4); // 6addTwoNumbers(2); // 2addTwoNumbers(); // 0
Symbols
Symbols have existed prior to ES6, but now we have a public interface to using them directly. Symbols are immutable and unique and can be used as keys in any hash.
Symbol();
Calling Symbol() or Symbol(description) will create a unique symbol that cannot be looked up globally. A Use case for Symbol() is to patch objects or namespaces from third parties with your own logic, but be confident that you won’t collide with updates to that library. For example, if you wanted to add a method refreshComponent to the React.Component class, and be certain that you didn’t trample a method they add in a later update:
Symbol.for(key) will create a Symbol that is still immutable and unique, but can be looked up globally. Two identical calls to Symbol.for(key) will return the same Symbol instance. NOTE: This is not true for Symbol(description):
A common use case for Symbols, and in particular with Symbol.for(key) is for interoperability. This can be achieved by having your code look for a Symbol member on object arguments from third parties that contain some known interface. For example:
function reader(obj) { const specialRead = Symbol.for("specialRead"); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError("object cannot be read"); }}
A notable example of Symbol use for interoperability is Symbol.iterator which exists on all iterable types in ES6: Arrays, strings, generators, etc. When called as a method it returns an object with an Iterator interface.
Maps is a much needed data structure in JavaScript. Prior to ES6, we created hash maps through objects:
var map = new Object();map[key1] = "value1";map[key2] = "value2";
However, this does not protect us from accidentally overriding functions with specific property names:
> getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');> TypeError: Property 'hasOwnProperty' is not a function
Actual Maps allow us to set, get and search for values (and much more).
let map = new Map();> map.set('name', 'david');> map.get('name'); // david> map.has('name'); // true
The most amazing part of Maps is that we are no longer limited to just using strings. We can now use any type as a key, and it will not be type-cast to a string.
Note: Using non-primitive values such as functions or objects won’t work when testing equality using methods such as map.get(). As such, stick to primitive values such as Strings, Booleans and Numbers.
We can also iterate over maps using .entries():
for (let [key, value] of map.entries()) { console.log(key, value);}
Promises
Promises allow us to turn our horizontal code (callback hell):
func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); }); });});
Into vertical code:
func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, (value5) => { // Do something with value 5 });
Prior to ES6, we used bluebird or Q. Now we have Promises natively:
new Promise((resolve, reject) => reject(new Error("Failed to fulfill Promise"))).catch((reason) => console.log(reason));
Where we have two handlers, resolve (a function called when the Promise is fulfilled) and reject (a function called when the Promise is rejected).
Benefits of Promises: Error Handling using a bunch of nested callbacks can get chaotic. Using Promises, we have a clear path to bubbling errors up and handling them appropriately. Moreover, the value of a Promise after it has been resolved/rejected is immutable - it will never change.
Here is a practical example of using Promises:
var request = require("request");return new Promise((resolve, reject) => { request.get(url, (error, response, body) => { if (body) { resolve(JSON.parse(body)); } else { resolve({}); } });});
We can also parallelize Promises to handle an array of asynchronous operations by using Promise.all():
let urls = [ "/api/commits", "/api/issues/opened", "/api/issues/assigned", "/api/issues/completed", "/api/issues/comments", "/api/pullrequests",];let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }).done((data) => { resolve(data); }); });});Promise.all(promises).then((results) => { // Do something with results of all our promises});
Generators
Similar to how Promises allow us to avoid callback hell, Generators allow us to flatten our code - giving our asynchronous code a synchronous feel. Generators are essentially functions which we can pause their execution and subsequently return the value of an expression.
A simple example of using generators is shown below:
Where next will allow us to push our generator forward and evaluate a new expression. While the above example is extremely contrived, we can utilize Generators to write asynchronous code in a synchronous manner:
// Hiding asynchronousity with Generators
function request(url) { getJSON(url, function (response) { generator.next(response); });}
And here we write a generator function that will return our data:
function* getData() { var entry1 = yield request("https://some_api/item1"); var data1 = JSON.parse(entry1); var entry2 = yield request("https://some_api/item2"); var data2 = JSON.parse(entry2);}
By the power of yield, we are guaranteed that entry1 will have the data needed to be parsed and stored in data1.
While generators allow us to write asynchronous code in a synchronous manner, there is no clear and easy path for error propagation. As such, as we can augment our generator with Promises:
function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve); });}
And we write a function which will step through our generator using next which in turn will utilize our request method above to yield a Promise:
function iterateGenerator(gen) { var generator = gen(); (function iterate(val) { var ret = generator.next(); if (!ret.done) { ret.value.then(iterate); } })();}
By augmenting our Generator with Promises, we have a clear way of propagating errors through the use of our Promise .catch and reject. To use our newly augmented Generator, it is as simple as before:
iterateGenerator(function* getData() { var entry1 = yield request("https://some_api/item1"); var data1 = JSON.parse(entry1); var entry2 = yield request("https://some_api/item2"); var data2 = JSON.parse(entry2);});
We were able to reuse our implementation to use our Generator as before, which shows their power. While Generators and Promises allow us to write asynchronous code in a synchronous manner while retaining the ability to propagate errors in a nice way, we can actually begin to utilize a simpler construction that provides the same benefits: async-await.
Async Await
While this is actually an upcoming ES2016 feature, async await allows us to perform the same thing we accomplished using Generators and Promises with less effort:
var request = require("request");function getJSON(url) { return new Promise(function (resolve, reject) { request(url, function (error, response, body) { resolve(body); }); });}async function main() { var data = await getJSON(); console.log(data); // NOT undefined!}main();
Under the hood, it performs similarly to Generators. I highly recommend using them over Generators + Promises. A great resource for getting up and running with ES7 and Babel can be found here.
Getter and setter functions
ES6 has started supporting getter and setter functions within classes. Using the following example:
class Employee { constructor(name) { this._name = name; } get name() { if (this._name) { return "Mr. " + this._name.toUpperCase(); } else { return undefined; } } set name(newName) { if (newName == this._name) { console.log("I already have this name."); } else if (newName) { this._name = newName; } else { return false; } }}var emp = new Employee("James Bond");// uses the get method in the backgroundif (emp.name) { console.log(emp.name); // Mr. JAMES BOND}// uses the setter in the backgroundemp.name = "Bond 007";console.log(emp.name); // Mr. BOND 007
Latest browsers are also supporting getter/setter functions in Objects and we can use them for computed properties, adding listeners and preprocessing before setting/getting:
var person = { firstName: "James", lastName: "Bond", get fullName() { console.log("Getting FullName"); return this.firstName + " " + this.lastName; }, set fullName(name) { console.log("Setting FullName"); var words = name.toString().split(" "); this.firstName = words[0] || ""; this.lastName = words[1] || ""; },};person.fullName; // James Bondperson.fullName = "Bond 007";person.fullName; // Bond 007
ES6 Modules
Prior to ES6, we used libraries such as Browserify to create modules on the client-side, and require in Node.js. With ES6, we can now directly use modules of all types (AMD and CommonJS).
Exporting in ES6
With ES6, we have various flavors of exporting. We can perform Named Exports:
export let name = 'David';export let age = 25;
As well as exporting a list of objects:
function sumTwo(a, b) { return a + b;}function sumThree(a, b, c) { return a + b + c;}export { sumTwo, sumThree };
We can also export functions, objects and values (etc.) simply by using the export keyword:
export function sumTwo(a, b) { return a + b;}export function sumThree(a, b, c) { return a + b + c;}
And lastly, we can export default bindings:
function sumTwo(a, b) { return a + b;}function sumThree(a, b, c) { return a + b + c;}let api = { sumTwo, sumThree,};export default api;/* Which is the same as * export { api as default }; */
Best Practices: Always use the export default method at the end of the module. It makes it clear what is being exported, and saves time by having to figure out what name a value was exported as. More so, the common practice in CommonJS modules is to export a single value or object. By sticking to this paradigm, we make our code easily readable and allow ourselves to interpolate between CommonJS and ES6 modules.
Importing in ES6
ES6 provides us with various flavors of importing. We can import an entire file:
import "underscore";
It is important to note that simply importing an entire file will execute all code at the top level of that file.
Similar to Python, we have named imports:
import { sumTwo, sumThree } from "math/addition";
We can also rename the named imports:
import { sumTwo as addTwoNumbers, sumThree as sumThreeNumbers,} from "math/addition";
In addition, we can import all the things (also called namespace import):
import * as util from "math/addition";
Lastly, we can import a list of values from a module:
import * as additionUtil from "math/addition";const { sumTwo, sumThree } = additionUtil;
Importing from the default binding like this:
import api from "math/addition";// Same as: import { default as api } from 'math/addition';
While it is better to keep the exports simple, but we can sometimes mix default import and mixed import if needed. When we are exporting like this:
// foos.jsexport { foo as default, foo1, foo2 };
We can import them like the following:
import foo, { foo1, foo2 } from "foos";
When importing a module exported using commonjs syntax (such as React) we can do:
import React from "react";const { Component, PropTypes } = React;
This can also be simplified further, using:
import React, { Component, PropTypes } from "react";
Note: Values that are exported are bindings, not references. Therefore, changing the binding of a variable in one module will affect the value within the exported module. Avoid changing the public interface of these exported values.
Additional Resources
In addition to those features of ES6+, you’ll notice other features that you can incrementally learn as you go along. Here’s an incomplete list.
The following was written for interns starting out with browsers at Clear Labs.
Javascript was initially developed as a scripting language for the browser. The language has expanded into servers, IoT devices, serverless functions. But let’s take it a step back and talk more about its initial use case with browsers.
Back in the early days of the Web, developers wanted to handle more than reading documents. Forms were introduced to start this interactivity, and soon, developers wanted more APIs. These set of APIs for browsers, known as DOM APIs, became the way a developer could interact with the browser using Javascript. Over the years, this has matured into a large set of APIs.
You can find a separate wiki page for the DOM APIs that we use for our app.
Performance
The DOM, or the document object model, is a representation of the HTML on the page. The browser parses the HTML and puts that HTML in a representation called the DOM. In addition, the browser also parses the CSS and places it in a similar representation known as the CSSDOM. When these two are complete, a paint event can occur which can be shown to the user.
Javascript’s execution is slightly different than HTML and CSS. If Javascript gets loaded prior to the CSSDOM completion, it could block the browser’s paint execution until that Javascript is loaded. This phenomenon, known as Blocking, has some effects on performance.
For a deeper dive into browser performance, here are two (paid) books.
High Performance Web Sites - Written in 2007, still holds value in how browsers run. Some syntax has been updated, but the general advice is sound. It is highly likely you can find this book for free
Even Faster Websites - Written in 2009, a good follow-up to “High Performance Web Sites” that tackles additional topics about Javascript, the Browser, and the Network
To understand blocking, you have to understand the event loop. The following resource is a great primer on the event loop.
What the heck is the event loop anyway? - A Youtube video conference talk on how the event loop works. It also goes over some special topics of multi-threading with Javascript.
Event Handling
One of Javascript’s purposes is to handle events from the user. You could write some code like this:
var input = document.getElementById("input-text-username");input.onchange((event) => { // Do something with the event console.log(event);});
The onchange attribute is function that takes a callback. A callback is a function that gets triggered when the event is triggered. Any event that takes place on the DOM can include a callback, for example, focus in on the element or mouseover the element.
This article was written as part of our initial docs. I have many more articles about React, and I’m debating whether I should cover them in a single article, or multiple. Stay tuned.
At Clear Labs, the web app is a front-end application built on top of React. React is a javascript library that, when paired with other libraries, creates a front-end framework. In our project, we have React on the front-end and nginx serving the assets on the back-end.
If you are starting React with no previous knowledge, please start with the official docs.
Once you have familiarized yourself with the library, play around with it on Codesandbox or on your local system using Create React App. If you can build yourself a basic UI, continue reading this wiki.
Base Foundation
To build with React, each developer should hone their vanilla Javascript knowledge. Please refer to the Javascript wiki to see if you have any missing gaps in your knowledge base.
A must for each developer onboarding is a clear understanding of how React works. This includes the following:
What are React’s lifecycle functions? And how are they supplemented with React hooks?
Why would I use a React class component over a functional component? And when?
This post tries to address these questions and many more.
Newer React Functionality (React v15+)
The application uses many techniques that are worth highlighting because we’ve developer our own set of best practices to follow.
React Context
React Hooks
React Performance APIs (useMemo, useCalllback)
Supporting Libraries
Many supporting libraries help support the development of the app. Most of these supporting libraries are open source and have dedicated wiki pages. Here are the highlights:
React-Final-Form
Downshift
d3
i18next
Luxon (migrating from moment)
Components
Our project includes Storybook, an interactive UI tool to develop and document components. In each component, an extra js file is created with the stories suffix. E.g. index.stories.js. This helps with developing components on their own and reduces overhead with creating component properties.
Refactoring class components to functional components
Lifecycle functions can be replaced with useEffect. But be careful, as we mentioned in useEffect vs useLayoutEffect, useEffect is asynchronous and lifecycle functions aren’t a 1
match.
componentDidMount() {// do something}// now becomesuseEffect(() => {// do something}, []);
Building Components
While the previous section introduced us to components, this section expands on how we write components.
Class or Function?
When creating a new component, start off with a function component. What is a function component?
const FunctionComponent = (props) => <div>Here's the JSX</div>;
A function component is an easier markup to read. To React, a function component vs. a class are indistinguishable. As developers, we aim for clean code. Ask yourself the following questions of whether you might need a class.
Do we need lifecycle functions? If this is yes, evaluate whether you can use Hooks instead. If not, use a class.
Do we need a constructor? Rarely do we need a constructor. If you need one ask what special cases are you doing to state or what the justification is for other constructor needs.
Does the component need private or public methods? On a rare occassion, we may want to expose a public class method. Use a class.
Maybe there are private methods a class should have. Use a class.
In general, for most components are function components. With the introduction of hooks, function components can also have state. We have our own section about hooks too.
Component or PureComponent?
If using a class, we can further ask whether a PureComponent should be extended vs. a Component.
Compound Components
Compound components allow you to create and use components which share this state implicitly.
Other Related Articles
I’ve written a few other React articles, as shown below:
At my new job, because we’re building the project from the ground up. The team decided to move forward with a css-in-js approach, which perked my ears. First I was skeptic. How could this remove my css files? What about psuedoelements and complex selectors.
Having worked with it for three months now, I’m a convert. Those initial skepticisms wore away as I started to write React with styled components in mind. Before I jump into a length explanation, let’s do a starter demo.
Intoduction through exercise
import React from "react";import styled from "styled-components";const Wrapper = styled.div` padding: 1rem;`;const MainContent = styled.div` font-size: 1.2rem; text-align: center;`;const App = (props) => ( <Wrapper {...props}> <MainContent> Lorem ipsum dolor sit amet, vim at quando possim oporteat, eu omnium apeirian argumentum per. </MainContent> </Wrapper>);
Brace expansion is used to generate arbitrary strings. The specified strings are used to generate all possible combinations with the optional surrounding preambles and postscripts.
Usually it’s used to generate mass-arguments for a command, that follow a specific naming-scheme.
:!: It is the very first step in expansion-handling, it’s important to understand that. When you use
echo {a,b}$PATH
then the brace expansion does not expand the variable - this is done in a later step. Brace expansion just makes it being:
echo a$PATH b$PATH
Another common pitfall is to assume that a range like ”{1..200}” can be expressed with variables using ”{$a..$b}”. Due to what I described above, it simply is not possible, because it’s the very first step in doing expansions. A possible way to achieve this, if you really can’t handle this in another way, is using the ”eval” command, which basically evaluates a commandline twice: eval echo {$a..$b} For instance, when embedded inside a for loop : for i in $(eval echo {$a..$b}) This requires that the entire command be properly escaped to avoid unexpected expansions. If the sequence expansion is to be assigned to an array, another method is possible using declaration commands: declare -a 'pics=(img{'"$a..$b"'}.png)'; mv "${pics[@]}" ../imgs This is significantly safer, but one must still be careful to control the values of $a and $b. Both the exact quoting, and explicitly including “-a” are important.
The brace expansion is present in two basic forms, string lists and ranges.
It can be switched on and off under runtime by using the ”set” builtin and the option ”-B” and ”+B” or the long option ”braceexpand”. If brace expansion is enabled, the stringlist in ”SHELLOPTIONS” contains ”braceexpand”.
String lists
{string1,string2,...,stringN}
Without the optional preamble and postscript strings, the result is just a space-separated list of the given strings:
$ echo {I,want,my,money,back}I want my money back
With preamble or postscript strings, the result is a space-separated list of all possible combinations of preamble, specified strings and postscript:
The brace expansion is only performed, if the given string list is really a list of strings, i.e. if there’s minimum one ””,”” (comma)! Something like ”{money}” doesn’t expand to something special, it’s really only the text ””{money}””.
Ranges
{<START>..<END>}
Brace expansion using ranges is written giving the startpoint and the endpoint of the range. This is a “sequence expression”. The sequences can be of two types
integers (optionally zero padded, optionally with a given increment)
characters
$ echo {5..12}5 6 7 8 9 10 11 12$ echo {c..k}c d e f g h i j k
When you mix these both types, brace expansion is not performed:
$ echo {5..k}{5..k}
When you zeropad one of the numbers (or both) in a range, then the generated range is zeropadded, too:
$ echo {01..10}01 02 03 04 05 06 07 08 09 10
There’s a chapter of Bash 4 brace expansion changes at [[#new_in_bash_4.0 | the end of this article]].
Similar to the expansion using stringlists, you can add preamble and postscript strings:
When you combine more brace expansions, you effectively use a brace expansion as preamble or postscribt for another one. Let’s generate all possible combinations of uppercase letters and digits:
Brace expansions can be nested, but too much of it usually makes you losing overview a bit ;-)
Here’s a sample to generate the alphabet, first the uppercase letters, then the lowercase ones:
{% assign special = '{{A..Z},{a..z}}' %}$ echo {{ special }}A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z
Common use and examples
Massdownload from the Web
In this example, ”wget” is used to download documentation that is split over several numbered webpages.
”wget” won’t see your braces. It will see 6 different URLs to download.
See the [[#news_in_bash_4.0 | text below]] for a new Bash 4 method.
Repeating arguments or words
somecommand -v -v -v -v -v
Can be written as
somecommand -v{,,,,}
…which is a kind of a hack, but hey, it works.
More fun
The most optimal possible brace expansion to expand n arguments of course consists of n’s prime factors. We can use the “factor” program bundled with GNU coreutils to emit a brace expansion that will expand any number of arguments.
function braceify { [[ $1 == +([[:digit:]]) ]] || return typeset -a a read -ra a < <(factor "$1") eval "echo $(printf '{$(printf ,%%.s {1..%s})}' "${a[@]:1}")"}printf 'eval printf "$arg"%s' "$(braceify 1000000)"
“Braceify” generates the expansion code itself. In this example we inject that output into a template which displays the most terse brace expansion code that would expand ”“$arg”” 1,000,000 times if evaluated. In this case, the output is:
After reading a lengthy comic from Lin Clark about WebAssembly,
I thought it might be great to do a deep dive with it.
Thank goodness the folks behind the project have a very nice Getting Started tutorial.
Prerequisites
To compile to WebAssembly, we will at the moment need to compile LLVM from source. The following tools are needed as a prerequisite:
I’m on OSX. Check the link for above for your prerequisites.
Git
CMake
Xcode
Python (2.7.x)
Compile Emscripten from Source
Building Emscripten from source is automated via the Emscripten SDK. The required steps are as follows.
After these steps, the installation is complete. To enter an Emscripten compiler environment in the current command line prompt, type
$ source ./emsdk_env.sh
This command adds relevant environment variables and directory entries to PATH to set up the current terminal for easy access to the compiler tools.
Note, the installation process will take a while. Go make yourself some tea.
To conveniently access the selected set of tools from the command line,consider adding the following directories to PATH, or call 'source./emsdk_env.sh' to do this for you.
Compile and run a simple program
We now have a full toolchain we can use to compile a simple program to WebAssembly. There are a few remaining caveats, however:
We have to pass the linker flag -s WASM=1 to emcc (otherwise by default emcc will emit asm.js).
If we want Emscripten to generate an HTML page that runs our program, in addition to the wasm binary and JavaScript wrapper, we have to specify an output filename with a .html extension.
Finally, to actually run the program, we cannot simply open the HTML file in a web browser because cross-origin requests are not supported for the file protocol scheme. We have to actually serve the output files over HTTP.
The commands below will create a simple “hello world” program and compile it. The compilation step is highlighted in bold.
To serve the compiled files over HTTP, we can use the emrun web server provided with the Emscripten SDK:
$ emrun --no_browser --port 8080 .
Once the HTTP server is running, you can open it in your browser. If you see “Hello, world!” printed to the Emscripten console, then congratulations! You’ve successfully compiled to WebAssembly!
Here’s how my logs looked when running the program
➜ hello-wasm emcc hello.c -s WASM=1 -o hello.htmlINFO:root:generating system library: libc.bc... (this will be cached in "/Users/Jeremy/.emscripten_cache/asmjs/libc.bc" for subsequent builds)INFO:root: - okINFO:root:generating system library: dlmalloc.bc... (this will be cached in "/Users/Jeremy/.emscripten_cache/asmjs/dlmalloc.bc" for subsequent builds)INFO:root: - okINFO:root:generating system library: wasm-libc.bc... (this will be cached in "/Users/Jeremy/.emscripten_cache/asmjs/wasm-libc.bc" for subsequent builds)INFO:root: - ok
Single responsibility means one unit of code does one thing.
It should not be responsibility for many different things.
In other words, it adheres to “separation of concerns”.
In dynamic languages like Javascript, there’s no typechecking what a function can return.
So, a function could take any arguments of any type and return any type.
In general, we want to avoid this. Take the following code example.
Our function, getPlayerBehavior, takes one input that should be a number
However, the first return statement returns a type String,
while the second return returns a type Array.
This is an example where the function is doing two different things in one
unit of code. We want to avoid this behavior and evaludate what this function should do.
Should it pass back the list of behavior names
or should it find the behavior given the index.
Take the following solution as a guideline.
// Same constantfunction getPlayerBehaviors() { return PLAYER_BEHAVIORS.map(playerBehavior => playerBehavior.name);}function findPlayerBehavior(index) { if (Number.isInteger(index)) { const playerBehavior = PLAYER_BEHAVIORS.find(playerBehaviour => playerBehaviour.id === index); if (playerBehavior) { return playerBehavior.name; } else { // Could not find player behavior return ''; } } else { throw new Error('index should be type Number:Integer'); }}
In this example, we have separated the function into two functions because they are doing two separate things.
Depending on how we called this function before, we will have to determine
which function to use before we make the function call.
After a year working with JSX syntax in React, I’ve learned a few things reviewing my code as well as other developer’s code.
I am taking the perspective of a code review in these following code samples.
Last fix I would like to see is to alphabetize the tag properties.
I don’t want to find a property if the list is random.
This is more so when the property list is greater than 10.
Some of you may notice the two self-closing tags are unnecessary.
As you may or may not be aware, we are using Bootstrap.
Bootstrap contains “offset” classes that can be used to replace those first two column <div>.
I didn’t address this because the developer may plan to use it.
It’s more simple for me to note that in my review notes to the developer than to tell them to make those changes.
If that developer chooses to keep it, they can leave a comment as to why it is left that way.
The Javascript ES2015 spec introduces object destructing. Object destructing used to pass a single object parameter instead of long argument lists to functions. Take the following code example.
Bad Code:
function enableListFilter(option, filterName, filterIndex, activeAccessTypeIndex) { // Do Something}enableListFilter(option, filterName, filterIndex, activeAccessTypeIndex);
The function requires the developer to know the order of the parameters passed into the function.
When this is one argument, that’s easy.
When it gets to 2 or more arguments, this can get difficult since that stretches a developer’s working memory a la “yet another thing to remember”.
Here’s an alternative.
Better Code:
function enableListFilter({ option, filterName, filterIndex, activeAccessTypeIndex }) { // Do Something}enableListFilter({ option: option, filterName: filterName, filterIndex: filterIndex, activeAccessTypeIndex: activeAccessTypeIndex});
If you want to reduce the number of lines, you can use the object parameter shorthand.
Note: A caveat of this approach your argument names must be the same name.
In most cases, an explicit approach of writing out object keys is better.
I’ve been a Javascript developer for the past year and a half. By reading the “You don’t Know Javascript” series, I’m hoping to hone my vanilla Javascript skills and have a deeper knowledge of the language.
In Up & Going, I’m hoping to review the basics and understand more deeply why the language has the set of tools it has, and perhaps a deeper understanding of why we write Javascript the way we do.
When you strive to comprehend your code, you create better work and become better at what you do.
Preface
This You Don’t Know JavaScript book series offers a contrary challenge: learn and deeply understand all of JavaScript, even and especially “The Tough Parts”.
I like this attitude as it focuses on the bits of the language I have to deal with time and time again. It helps me understand the behavior of Javascript without blindly looking at a TypeError with a quizzical expression.
i.e. Cut out the buzzwords. Learn the language
Chapter 1: Into Programming
Explains programming at a high level. I may skip large sections of this.
literal value are values that are itself and are not stored. e.g. in the statement, y = x + 4;, 4 is a literal value.
Expressions are the reference to variables, values, or set of variable(s) and value(s) combined with operators.
Assignment expressions assign a variable to another expression
I’m reviewing this because these basic building blocks can be fundamentally different. In Go, assignments can be completely different
Review later: Javascript compiling in the first two chapters of Scope & Closures
The prompt() function opens an alert with an input. You can assign the function with a variable. age = prompt("What is your age?");
We take it for granted that specifying the variable in an assignment is typically on the left.
Side tangent: Are there languages that do the opposite?
Some lesser known assignments in JS include
Remainder assignment x %= y
Shift assignments x <<= y
Shift bits left or right by a certain amount. The above example shifts bits to the left.
Bitwise assignments x &= y or x = x & y
The above example pertains to bits, using an AND logic.
AND logic table
Bit
Bit
Result
0
0
0
1
0
0
0
1
0
1
1
1
values added to the source code are called literals
Implicit coersion is when you’re making a comparison against two different types. The == operation is ‘loosely equal’ and uses implicit coersion. For this reason, it should be avoided because it can cause unexpected bugs.
More on this later in Chapter 2 of this title & Chapter 4 of Types & Grammar
Code comments help other humans understand your code. It’s a communication point.
Comments should explain why, not what. They can optionally explain how if that’s particularly confusing.
Note to self - focus on why, and less on what.
Static Typing - variables adhere to type enforcement
Dynamic Typing - allows a variable to represent a value regardless of type. Javascript adheres to this.
State is tracking changes to values as the program runs. In redux, we keep a global state to track the user’s progress through the application.
Constants are declared once and don’t change. In ES6, when you declare a constant once, it throws an error if there is an attempt to change it. This is like the static-tying type enforcement.
A group of series of statements strung together are called a block. A block is contained within curly brackets. { .. }
In Ruby, there are different ways to show a block. In fact, there are different types of blocks, like your general block, procs, and lambdas.
Conditions throw an error if its expression between the parentheses are not a boolean.
“Falsy” values are converted to the boolean false. “Truthy” values do the opposite. More on this in Chapter 4 of Types & Grammar
An iteration occurs each time a block is called.
Warning: For a variety of historical reasons, programming languages almost always count things in a zero-based fashion, meaning starting with 0 instead of 1. If you’re not familiar with that mode of thinking, it can be quite confusing at first. Take some time to practice counting starting with 0 to become more comfortable with it!
I rarely use do..while loops. Here’s the syntax.
do { console.log('How may I help you?'); // Help the customer numOfCustomers = numOfCustomers - 1;} while (numOfCustomer > 0);
Like C, the Javascript for loop has three clauses.
The initialization clause
The conditional clause
The update clause
Reusable pieces of code can be gathered into a function
The lexical scope, or scope, is the programming term to tell us where our variables can be accessed.
function outer() { var a = 1; function inner() { var b = 2; // we can access both `a` and `b` here console.log( a + b ); // 3 } inner(); // we can only access `a` here console.log( a ); // 1}outer();
In the example, you can’t call inner(); on the outermost scope. It can only be called within the outer function scope, as shown.
More on lexical scope in the first three chapters of Scope & Closures
Chapter 2. Into Javascript
Note: After reading through the first chapter, I realize I don’t really need to review too much. I’m going to skim this chapter and only note the things that I really think are worthwhile. Otherwise, I will keep notes on this chapter to a minimum.
No value set type is undefined.
I didn’t know null is an object type. Weird bug probably will never get fixed.
typeof null is an interesting case, because it errantly returns “object”, when you’d expect it to return “null”.
To learn: Javascript’s symbols. I’m well aware of Ruby’s implementation of symbols like :symbol_example, which are used in many different contexts like classes. Will elaborate more on this in the ES6 portion.
Arrays and functions are subtypes to objects. In my introduction to JS, I assumed “everything is an object”.
Built-In Type Methods extend the power of Javascript. These methods are like String.prototype.toUpperCase.
Coercion comes in two forms: explicit and implicit
explicit is with both types the same.
implicit is when type conversion can happen.
Coercion is not evil. There are times when you may need to convert types.
List of falsy:
"" (empty string)
0, -0, NaN
null, undefined
false
Things that can be truthy
non-empty strings
non-zero, valid numbers
true
arrays, empty or non-empty
objects, empty or non-empty
functions
== checks for value equality. Coercion allowed.
=== checks for value and type equality. Coercion not allowed. Also known as strict equality
Some simple rules for equality of when to use == or ===.
If either value (aka side) in a comparison could be the true or false value, avoid == and use ===.
If either value in a comparison could be of these specific values (0, "", or [] — empty array), avoid == and use ===.
In all other cases, you’re safe to use ==. Not only is it safe, but in many cases it simplifies your code in a way that improves readability.
I’ve played it safe with this, but I may revisit using == more often, if it doesn’t violate the rules. Important note: think critically before use.
You might think that two arrays with the same contents would be == equal, but they’re not
When comparing numbers with strings, the strings get coerced into a number. When the string contains non-number characters, it gets converted to NaN and when comparing with < or >, NaN is neither greater nor less than a value, hence returns false.
Hoisting is when a variable is moved to the top of the enclosing scope. (conceptually speaking)
Okay to use a function before it is declared as function declarations are hoisted. Generally not good with variables.
Use let for block scoped variables. For example, in an if block, you declare a variable you only want to be used within that block, use let.
function foo() { var a = 1; if (a >= 1) { let b = 2; while (b < 5) { let c = b * 2; b++; console.log( a + c ); } }}foo();// 5 7 9
Strict mode was introduced in ES5. Noted with "use strict".
Strict mode disallows implicit auto-global variable declaration from omitting the var.
I feel computer science needs to put unnecessarily long titles to items. Immediately invoked function expressions (IIFE) are involved upon declaration.
An example use case was with Highcharts and creating an options object. You can’t always assign a key with a function, so this is one way around it.
Closure is a way to “remember” and continue to access a function’s scope.
I think of this as a way to tweak functions without having to write out more functions.
This is least understood by JS developers, and I think I know why. To me, it’s a function generator, although that’s an improper term because Javascript can create a generator function, which is a totally different topic.
The most common usage of closure in Javascript is the module pattern. Modules let you define private implementation details (variables, functions) that are hidden from the outside world, as well as a public API that is accessible from the outside.
Executing the module as a function creates an instance of that module.
The this operator reference depends on how the function is called. That will determine what this is. There are four rules of how this gets set. More on this later in the this & Object Prototype book.
Prototype links allow one object to delegate properties from another object. What this means is a property prototype linked is not attached to that object but to its original object (which could in turn, just be the proto property of Object).
Do not think of applying prototypes as inheritance. It follows a pattern called “behavior delegation”, delegating one part of an object to another.
Bring the old to new with polyfiling and transpiling.
A “polyfill” is to take a definition of a newer feature and produce a piece of code equivalent to the behavior for older JS environments.
An example is lodash that has a bunch of features from ES5 and ES6 which some frameworks utilize, like forEach and map.
Careful writing your own polyfill as you should adhere closely to the specification.
Better yet, use the vetted ones.
Transpiling is great for new syntax. It is a tool that converts your code to older coding environments. You can break down the word “transpiling” into transforming + compiling.
arguments can be used functions to determine which arguments were passed in. It is not a reserved word, so you can assign it to a different value. When calling it, it outputs an array.
The book series doesn’t cover Non-Javascript, like the DOM API. But you need to be aware of it. DOM API could actually be written by the browsers in C/C++.
The document object is a “host object”, a special object that has been implemented by the browser.
Chapter 3: Into YDKJS
This chapter is a preface to the other books. I’ll skip these notes as I’ll be covering this in more detail in those posts.
The Levenshtein Distance problem, also known as the edit distance problem, is to find the lowest cost path to turn one string to another string. It is commonly used in spell checking, Google search results for ‘did you mean…’, and DNA sequencing (Bioinformatics).
An easy example is if you misspelled “word” for “wird”, there would need to be one replacement to get from “wird” to “word” (change the letter “i” to “o”). There is one algorithm that answers this problem efficiently, as we now of right now. This algorithm compares one letter of the first string to all of the characters to the other strings and gives it a ranking or a score. There are three ways you can modify a character, by inserting another character, deleting, or replacing that character. Each of these modifications cost one value.
Typically, you will see this as a matrix, as shown above. The first row and first column show the length of the strings. The rest of the matrix is filled out following that you do a nested for loop, taking one letter at a time for the first string and go through the rest of the letters in the second string. Here’s a quick look with Ruby:
(1..second.length).each do |i| (1..first.length).each do |j| # Do your logic here endend
We traverse this matrix starting at [1][1] (one square diagonal of the top left square), and move to our right until we get to the end of the matrix. Then we continue to the next row and go back to 1 (matrix [2][1]). We place a conditional in our code. If the letters we are comparing are the same, then there is no cost penalty and we take the same value as the diagonal on the square’s top left. However, if it is different, we take the minimum cost to each of our modification choices.
Insertion: When we insert a letter in our first string, that shifts our word column, so we take the same value as to the square to its top, plus 1 for an additional modification cost.
Deletion: Same as insertion, but we take the value of the square to its left
Replace: We take the value of the upper left to the square and add 1 to it.
The result is at the bottom right corner, which will tell you the minimum cost of changing one string to another. If the score threshold is low enough, Google will ask you, “did you mean…” if the search ran through the algorithm and could match a more common search term.
A side note
My coding club looked at this algorithm with no prior knowledge of this problem and tried to understand it. Now that I’ve taken the time to review this, I have a much deeper understanding of this now.
Resources
Levenshtein distance implementation in different programming languages
MIT course on Algorithms: Levenshtein distance problem explained with dynamic programming