Code inComplete https://codeincomplete.com/ Recent content on Code inComplete Hugo -- gohugo.io en-us jake@codeincomplete.com (Jake Gordon) jake@codeincomplete.com (Jake Gordon) Sat, 01 Jan 2011 00:00:00 +0000 AI, ML, and Large Language Models https://codeincomplete.com/articles/ai-and-large-language-models/ Fri, 28 Jul 2023 00:00:00 +0000 jake@codeincomplete.com (Jake Gordon) https://codeincomplete.com/articles/ai-and-large-language-models/ <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/prediction.png" alt=""> </div> <br> <p>I often describe myself as a generalist software engineer with a broad range of experience in web app development, system development, front end, back end, devops, etc, but I must confess I have a somewhat blind spot when it comes to data science.</p> <p>In the past, I&rsquo;ve worked with machine learning at <a href="https://eagleview.com">Eagleview</a>, constructed analytic dashboards at <a href="https://www.liquidplanner.com/">LiquidPlanner</a>, and tackled an array of random business queries with hastily assembled SQL, R, or Excel at different points in my career. However, the recent surge in AI interest has left me feeling somewhat like I&rsquo;m missing out.</p> <p>Therefore, I&rsquo;ve been dedicating some time to getting back up to speed with machine learning and - unsurprisingly given the current hype - with large language models and natural language processing. You can find some of my thoughts on the subject below&hellip;</p> <div class='clear'></div> <hr class='separator'> <h2 id="artificial-intelligence">Artificial Intelligence</h2> <p>The term AI covers a <strong>wide</strong> range of topics:</p> <ul> <li>Natural Language Processing</li> <li>Computer Vision</li> <li>Robotics</li> <li>Expert Systems</li> <li>Self Driving Cars</li> <li>Generative AI</li> <li>AGI - Artificial <strong>General</strong> Intelligence</li> </ul> <p>When applied correctly, AI techniques can help solve or enhance a variety of business problems across numerous domains. From a <strong>software</strong> engineering perspective, our primary concerns are:</p> <ul> <li> <p><strong>Machine Learning</strong> - the ability to train a model to learn from data and make predictions or decisions without the need for explicitly structured programming</p> </li> <li> <p><strong>Deep Learning</strong> - a subset of machine learning that utilizes neural networks with &ldquo;deep&rdquo; layers, enabling more complex learning</p> </li> <li> <p><strong>Large Language Models</strong> - a subset of deep learning trained with a substantial corpus of natural language content, that develops a robust semantic understanding of language structure. This can be used to accomplish numerous text-related tasks, even when not explicitly trained for those tasks</p> </li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="traditional-machine-learning">Traditional Machine Learning</h2> <p>Traditional machine learning algorithms have been successfully used for decades. These algorithms perform well, boasting robust implementations in machine learning libraries like <a href="https://scikit-learn.org/stable/">scikit-learn</a>. They should probably remain the go-to solutions for many problems, especially when dealing with structured tabular data, where decision trees (and ensemble techniques like random forests or gradient boosting) continue to prove more accurate than deep learning techniques.</p> <ul> <li><strong>Linear Regression</strong> - predicts real values (e.g. house prices)</li> <li><strong>Logistic Regression</strong> - classifies discrete values (e.g. yes/no, cat/dog/rabbit)</li> <li><strong>Decision Trees</strong> - used for both classification and regression</li> <li><strong>Random Forests</strong> - an ensemble of simpler decision tree models</li> <li><strong>Gradient Boosting</strong> - another ensemble technique</li> <li><strong>Naive Bayes</strong> - probability classification using Bayes theorem (e.g. spam detection)</li> <li><strong>K Nearest Neighbours</strong> - solve regression or classification problems using locality</li> <li><strong>Collaborative Filtering</strong> - used for recommendation systems</li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="deep-learning-and-neural-networks">Deep Learning and Neural Networks</h2> <p>&hellip; but in the last decade or so, deep learning has taken center stage, using neural networks that can decipher complex non-linear relationships given sufficient parameters and training data. A neural network is considered a <strong>universal function approximator</strong> - signifying that it can be trained to approximate the result of any function.</p> <p>A neural network can be defined by:</p> <ul> <li><strong>architecture</strong> - the selection and arrangement of the network layers</li> <li><strong>parameters</strong> - the trained weights used at inference time</li> <li><strong>hyperparameters</strong> - the parameters employed during training (e.g. learning rate)</li> </ul> <p>There exists a variety of neural network architectures, each designed to handle specific tasks in the field of artificial intelligence:</p> <ul> <li>Feed-forward neural networks <strong>(FFNN)</strong> - the traditional densely layered network</li> <li>Convolution neural networks <strong>(CNN)</strong> - primarily used for image processing</li> <li>Recurrent neural networks <strong>(RNN)</strong> - ideal for text and time series data</li> <li>Long short-term memory networks <strong>(LSTM)</strong> - suitable for tasks involving long sequences</li> <li>Generative adversarial networks <strong>(GAN)</strong> - capable of augmenting and generating content</li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="the-transformer-architecture">The Transformer Architecture</h2> <div class='pull-right-md'> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/transformer.png" alt=""> </div> <p>A 2017 Google paper - <a href="https://arxiv.org/pdf/1706.03762.pdf">Attention is all you need</a> introduced a novel type of neural network architecture known as the transformer.</p> <p>This was designed to excel at text transformation tasks, such as language translation, and could be trained on a large corpus of text using unsupervised (e.g. unlabelled) training techniques like masking words and training the model to predict the missing word.</p> <p>Its proficiency in <strong>text transformation</strong> enables it to easily:</p> <ul> <li>transform language</li> <li>modify tone</li> <li>adjust personality</li> <li>alter structure</li> <li>correct grammar and spelling</li> </ul> <div class='clear'></div> <p>In addition to <strong>text transformation</strong> the transformer architecture also excels at:</p> <ul> <li><strong>text summarization</strong> - summarizing the semantic meaning</li> <li><strong>text completion</strong> - filling in a missing word or completing a sentence</li> <li><strong>text classification</strong> - identifying the subject, language, etc</li> <li><strong>sentiment analysis</strong> - determine the positive/negative sentiment</li> <li><strong>entity recognition</strong> - extracting nouns (e.g. people and place names)</li> <li><strong>action recognition</strong> - identify verbs (actions)</li> </ul> <p>Surprisingly, a language model employing the transformer architecture, when provided with sufficient training data and configured with a large number of parameters, begins to exhibit <strong>emergent behaviour</strong> that can <strong>appear intelligent</strong>.</p> <ul> <li> <p><strong>Information retrieval</strong> - with the right input (prompt), a language model can answer questions about its training data, or about the content provided in the prompt.</p> </li> <li> <p><strong>Chain of thought reasoning</strong> - again with carefully structured input, a language model can be guided to break down a task and use multiple steps to arrive at an answer.</p> </li> <li> <p><strong>Autonomous agency</strong> - with careful prompting, a language model can transform natural language tasks into programmatic actions that can be used to query or invoke external resources.</p> </li> </ul> <p>This marked the advent of the <strong>Large Language Model</strong>, and - perhaps - some of the first real signs of emerging artificial intelligence&hellip;</p> <div class='clear'></div> <hr class='separator'> <h2 id="autocomplete-vs-agi">AutoComplete vs AGI</h2> <p>&hellip; However, it&rsquo;s crucial to understand that a large language model is - at its heart - a <strong>predictive text model</strong> that:</p> <ul> <li>is trained with a large corpus of natural language text</li> <li>learns a semantic understanding of language</li> <li>understands the relationships between words</li> <li>&hellip; and becomes <strong>EXCEPTIONALLY GOOD AT PREDICTING THE NEXT WORD</strong></li> </ul> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/prediction.png" alt=""> </div> <p>Yes, an LLM can provide many useful abilities that, I believe, can significantly improve our applications and their user experience for the better. And yes, an LLM can exhibit some emergent behaviors that appear intelligent if trained or prompted under controlled circumstances. However, an LLM on its own:</p> <ul> <li>&hellip; does not think</li> <li>&hellip; does not reason</li> <li>&hellip; does not reflect</li> <li>&hellip; does not interact with the world</li> <li>&hellip; does not learn (beyond its training)</li> <li>&hellip; is not conscious</li> <li>&hellip; is very, very, far from <strong>AGI</strong> (artificial general intelligence)</li> </ul> <p>&hellip; but these models certainly <strong>are</strong> a powerful tool to add to our software development toolbelt.</p> <div class='clear'></div> <hr class='separator'> <h2 id="the-llm-landscape">The LLM Landscape</h2> <p>Let&rsquo;s explore the current large language model landscape with the help of <a href="https://arxiv.org/pdf/2303.18223.pdf">a survey of large language models</a>. The diagram below demonstrates the evolution of OpenAI&rsquo;s GPT models, culminating in the recent release of GPT-4, which is available in ChatGPT, BingAI, and the Open AI platform.</p> <p><br> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/openai.png" alt=""> </div> <br> </p> <p>However, GPT is not the only game in town. This diagram, also derived from <a href="https://arxiv.org/pdf/2303.18223.pdf">a survey of large language models</a>, was published merely a month ago (at the time of writing) and is already outdated due to last week&rsquo;s release of Meta&rsquo;s <a href="https://ai.meta.com/llama/">Llama 2</a> and Stability AI&rsquo;s <a href="https://stability.ai/blog/freewilly-large-instruction-fine-tuned-models">Free Willy 2</a>.</p> <p>The diagram illustrates the evolutionary paths of various large models from prominent companies in recent years. Given the excitement in the field, new models are being released continuously.</p> <p><br> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/landscape.png" alt=""> </div> <br> </p> <p>Some models are commercial, while others are open source. However it&rsquo;s necessary to carefully review each model&rsquo;s license to ascertain whether it can be utilized for your tasks.</p> <center> <table> <thead> <tr> <th>Commercial</th> <th></th> <th></th> <th>Open</th> <th></th> </tr> </thead> <tbody> <tr> <td><a href="https://openai.com/gpt-4">GPT</a></td> <td>(Open AI)</td> <td></td> <td><a href="https://ai.meta.com/llama/">LLAMA 2</a></td> <td>(Meta)</td> </tr> <tr> <td><a href="https://www.anthropic.com/product">CLAUDE</a></td> <td>(Anthropic)</td> <td></td> <td><a href="https://crfm.stanford.edu/2023/03/13/alpaca.html">ALPACA</a></td> <td>(Stanford)</td> </tr> <tr> <td><a href="https://bard.google.com/">PALM/BARD</a></td> <td>(Google)</td> <td></td> <td><a href="https://www.mosaicml.com/blog/mpt-7b">MPT</a></td> <td>(Mosaic ML)</td> </tr> <tr> <td><a href="https://www.bing.com/?/ai">GPT/BING</a></td> <td>(Microsoft)</td> <td></td> <td><a href="https://stability.ai/blog/freewilly-large-instruction-fine-tuned-models">FREE WILLY 2</a></td> <td>(Stability AI)</td> </tr> <tr> <td><a href="https://github.com/features/copilot">COLPILOT</a></td> <td>(GitHub)</td> <td></td> <td><a href="https://github.com/openlm-research/open_llama">OPEN LLAMA</a></td> <td>(Berkeley)</td> </tr> <tr> <td></td> <td></td> <td></td> <td><a href="https://arxiv.org/pdf/2211.05100.pdf">BLOOM</a></td> <td>(Big Science)</td> </tr> <tr> <td></td> <td></td> <td></td> <td><a href="https://huggingface.co/blog/bert-101">BERT</a></td> <td>(Google)</td> </tr> <tr> <td></td> <td></td> <td></td> <td><a href="https://huggingface.co/docs/transformers/model_doc/t5">T5</a> and <a href="https://huggingface.co/docs/transformers/model_doc/flan-t5">FLAN-T5</a></td> <td>(Google)</td> </tr> </tbody> </table> </center> <p>The latest advancements on the commercial side are <strong>GPT-4</strong> and <strong>Claude</strong>, while on the open-source side, the recent releases of <strong>Llama2</strong>, <strong>Free Willy 2</strong>, and <strong>MPT</strong> are generating excitement. This information will likely be outdated by the time you read this article :-)</p> <p>There are numerous models being published every week&hellip;</p> <ul> <li><a href="https://arxiv.org/pdf/2303.18223.pdf">A survey of LLMs</a></li> <li><a href="https://huggingface.co/models">Huggingface Model Hub</a></li> <li><a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">Huggingface Open LLM Leaderboard</a></li> </ul> <p><a href="https://huggingface.co/">huggingface.co</a> is a tech company that originally provided the open-source transformer library for building transformer networks in Python. Now it hosts many open models, provides open datasets, and is evolving into a community for open-source LLM knowledge sharing and tools. They offer a portal to search for models as well as a leader board tracking the performance of various metrics across the most popular models.</p> <div class='clear'></div> <hr class='separator'> <h2 id="prompt-engineering">Prompt Engineering</h2> <blockquote class="aside"> NOTE: The prompt examples in this section come from the exceptional online course <a href="https://www.coursera.org/lecture/generative-ai-with-llms/course-introduction-9uWab">Generative AI with Large Language Models</a> from Andrew Ng&rsquo;s deeplearning.ai </blockquote> <br> <p>If you are reading this article <em>(thank you)</em> - you have likely used ChatGPT and already have a good understanding of what it means to prompt a language model. The most fundamental prompt often comes directly from a user. The prompt is sent to the model, and the LLM attempts to <strong>COMPLETE THE TEXT WITH THE MOST PROBABLE SUBSEQUENT WORDS</strong>. Thus, it&rsquo;s crucial to understand that the LLM isn&rsquo;t exactly &ldquo;answering the question&rdquo; as we might perceive it. Instead, it uses the knowledge it acquired from its training data to predict the set of words that best completes the response.</p> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/basic-prompt.png" alt=""> </div> <br> <p>Without structure, a simple prompt coming directly from a user can cause an LLM to face some serious challenges:</p> <ul> <li>they don&rsquo;t have access to facts that occurred after their training date</li> <li>they don&rsquo;t have a real model of the world, so they are often incorrect</li> <li>they are frequently prone to hallucinations</li> </ul> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/model-problems.png" alt=""> </div> <p><br> To mitigate these issues, we employ a process called prompt engineering.</p> <p>Being a predictive text model, an LLM responds effectively if the question or instruction aligns with its training data. Prompt engineering is the art of structuring the prompt to guide the model to perform the desired task. In the example below, we provide an instruction to &ldquo;Classify this review&rdquo; accompanied by a hint that the text should be followed by the sentiment, but we do not include any examples. This is called <strong>zero-shot inference</strong>:</p> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/zero-shot-inference.png" alt=""> </div> <p>If the model struggles to perform the task, we can enhance the prompt to include a single example. This is called <strong>one-shot inference</strong>:</p> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/one-shot-inference.png" alt=""> </div> <p>If the model continues to struggle, we can improve the prompt to include multiple examples. In the example below we include both a positive and a negative example to guide the model towards performing the task we desire, in this case sentiment analysis. This is called <strong>few-shot inference</strong>:</p> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/few-shot-inference.png" alt=""> </div> <p>In more complex cases, for example (below), if we are using the LLM to perform math or draw a conclusion, our one-shot example can include a breakdown of the steps required to reach the answer. In this case our example explains <strong>why</strong> the answer is 11.</p> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/chain-of-thought.png" alt=""> </div> <p>Again, it&rsquo;s crucial to understand that the LLM is not making any mathematical calculations here. Our one shot example is broken down into smaller steps which guides our model to respond with a similar structure and break its own task down into smaller steps. Simpler steps equate to easier math, and easier math is much more likely to be guessed correctly by the model.</p> <p>So there are various techniques, suggestions, and patterns that you can use to structure the prompt to guide the model to perform the desired task, including:</p> <ul> <li>use clear and specific instructions</li> <li>use delimiters to indicate distinct sections</li> <li>include examples of the desired task and its outcome</li> <li>specify the steps required to complete a task</li> <li>instruct the model to derive intermediate steps</li> <li>instruct the model to focus on key aspects</li> <li>instruct the model to evaluate its own answer and improve it</li> <li>instruct the model to constrain the output length</li> <li>instruct the model to provide structured output</li> </ul> <p><br> <blockquote class="aside"> In all honesty, this feels a lot like early SEO (search engine optimization), where various patterns emerged to try to rank at the top of the Google search results page. Some of the patterns were based on the underlying algorithm, while others were a bit more speculative - YMMV. </blockquote> </p> <div class='clear'></div> <hr class='separator'> <h2 id="llm-memory-or-lack-thereof">LLM Memory (or lack thereof)</h2> <p>One complication of working with large language models is that they possess no real ability to retain information. They do not learn from their tasks (ChatGPT has to include the recent conversation for context), they only have general knowledge up to their training cut-off date, and they have a limited context window (prompt) size for assimilating new knowledge.</p> <ul> <li>Can the model summarize &ldquo;War and Peace&rdquo; - <strong>YES</strong> <ul> <li>it was part of the training data</li> </ul> </li> <li>Can the model summarize the new Stephen King novel - <strong>NO</strong> <ul> <li>it has no existing knowledge of it</li> <li>and it doesn&rsquo;t fit into the context window</li> </ul> </li> </ul> <p>For simple tasks, the solution to this problem might involve breaking the task down into sub-tasks. So for this example we could ask the model to summarize each chapter (or whatever portion fits into the context window) and then ask the model to summarize the summaries.</p> <p>However, more complex tasks necessitate other strategies&hellip;</p> <h2 id="rag---retrieval-augmented-generation">RAG - Retrieval Augmented Generation</h2> <p>One approach to handling the context window limitation involves storing our company documents in a vector database that can be used to perform a semantic search for the documents related to the user&rsquo;s question.</p> <ol> <li>The user asks a natural language question</li> <li>A semantic search is performed, returning related documents</li> <li>The LLM is provided with the original question plus the related documents</li> <li>A natural language answer is returned to the user</li> </ol> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/rag-orchestration.jpg" alt=""> </div> <p>In this way, we only send a small, relevant subset of our full documentation that is compact enough to fit into the model&rsquo;s context window.</p> <h2 id="pal---program-aided-language-model">PAL - Program Aided Language Model</h2> <p>A different, and potentially more powerful pattern, involves augmenting the LLM with an external orchestrator that can perform actions on its behalf.</p> <ol> <li>The user asks a natural language question</li> <li>The LLM transforms the question into an action (or query)</li> <li>The action is performed by the orchestrator</li> <li>The LLM transforms the results into a natural language answer</li> <li>The natural language answer is returned to the user</li> </ol> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/pal-orchestration.jpg" alt=""> </div> <p>In this way, we are only using the LLM as a text transformation engine, transforming the user&rsquo;s natural language request into actions that our application can perform - such as calling a function, querying a database, or making an API request - and then transforming our application&rsquo;s responses back into natural language. I believe this could be a very powerful orchestration pattern, avoiding many of the complexities of working with an LLM.</p> <h2 id="training-a-model">Training a Model</h2> <p>Many of the prompt engineering and RAG patterns are workarounds for the fact that:</p> <ul> <li>the LLM has no knowledge of our specific facts</li> <li>the LLM context window is too small to teach it everything</li> </ul> <p>A more robust solution is to train your own model, but that can be expensive and time consuming. Alternatively, you can fine tune a pre-trained model. This is much more efficient, and there are various mechanisms for undertaking fine tuning:</p> <ul> <li>Full instruction fine tuning</li> <li>Parameter efficient fine tuning (PEFT)</li> <li>Reinforcement learning from human feedback (RLHF)</li> </ul> <div class="contained"> <img src="https://codeincomplete.com/articles/ai-and-large-language-models/model-training.png" alt=""> </div> <p>Once you start down the path of training (or fine tuning) your own model, you will need a robust way to measure the performance - e.g. accuracy - of your model output. There are a number of different metrics, each designed to measure accuracy for different tasks:</p> <ul> <li><a href="https://aclanthology.org/W04-1013.pdf">ROUGE</a> - assess the quality of text summaries</li> <li><a href="https://aclanthology.org/P02-1040.pdf">BLEU</a> - asses the quality of text translations</li> <li><a href="https://openreview.net/pdf?id=rJ4km2R5t7">GLUE</a> and <a href="https://super.gluebenchmark.com/">SUPERGLUE</a> - benchmarks for common tasks</li> <li><a href="https://arxiv.org/pdf/2206.04615.pdf">BIG BENCH</a> and <a href="https://crfm.stanford.edu/helm/latest/">HELM</a> - additional benchmarks for common tasks</li> <li><a href="https://arxiv.org/pdf/2009.03300.pdf">MMLU</a> - benchmarks for standardized tests (SAT, law, math, etc)</li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="technical-challenges">Technical Challenges</h2> <p>Large language models are not a silver bullet. There are a number of technical challenges, not least of which is the cost to train and host the model.</p> <ul> <li><strong>cost</strong> - the compute budget to train and run models can be prohibitive</li> <li><strong>accuracy</strong> - ensuring accurate output requires investment</li> <li><strong>alignment</strong> - ensuring output is helpful, honest, harmless requires investment</li> <li><strong>speed</strong> - inference speed can be slow</li> <li><strong>recent knowledge</strong> - post-training general knowledge is unavailable</li> <li><strong>private knowledge</strong> - private facts are unavailable and must be taught</li> <li><strong>limited context window</strong> - ability to include private facts is limited by size</li> <li><strong>no in-built reasoning</strong> - careful prompt engineering must be performed</li> <li><strong>no in-built autonomy</strong> - workflow orchestration must be built</li> </ul> <p>However, given the growing interest and availability of open source models, experiments, prototypes, and concepts can be explored fairly quickly and easily.</p> <div class='clear'></div> <hr class='separator'> <h2 id="random-ideas">Random Ideas</h2> <p>Using language models to provide a natural language user experience (UX) is a very different approach to building applications than traditional forms and rules-based UX. However, now that we have a tool that allows us to understand the semantic meaning behind natural language, you can easily start to imagine new ways of providing values, and new feature and product ideas&hellip;</p> <ul> <li><strong>Consumer Reports</strong> - <em>&ldquo;Show me the top 3 fridges? must be quiet and energy efficient&rdquo;</em></li> <li><strong>Patents</strong> - <em>&ldquo;Find me existing patents that involve the development of super conductors&hellip;&rdquo;</em></li> <li><strong>Analytics</strong> - <em>&ldquo;Give me the top 10 sales, week over week, for the last 12 months&hellip;&rdquo;</em></li> <li><strong>Contracts</strong> - <em>&ldquo;What does this contract mean to me?&rdquo;</em>, <em>&ldquo;Which sections should I worry about?&rdquo;</em></li> <li><strong>Resumes</strong> - <em>&ldquo;Tell me more about Jake&rsquo;s experience with Rust?&rdquo;</em></li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="development-technologies">Development Technologies</h2> <p>There are a lot of technologies that enable this eco-system. At the top of the chain is the Open AI platform. Their API and cookbook are driving a lot of innovation. Fast.ai provides a very valuable training course and a high level library for experimentation. Hugging Face is becoming THE hub for the open-source deep learning community, hosting models, datasets and more. Langchain is the latest exciting agent/orchestrator library of the moment, but the underlying python library eco system is vast with fundamentals provided by numpy, pandas, matplotlib, tensorflow, pytorch, keras, and scikit. However, python is not the only language embracing ML. ELixir has a growing machine learning community with Nx, Scholar, Axon, and Bumblebee&hellip;</p> <ul> <li><a href="https://platform.openai.com/">Open AI Platform</a></li> <li><a href="https://github.com/openai/openai-cookbook">Open AI Cookbook</a></li> <li><a href="https://docs.fast.ai/">Fast AI library</a></li> <li>Hugging Face <a href="https://huggingface.co/models">Models</a>, <a href="https://huggingface.co/docs/datasets/index">Datasets</a>, <a href="https://huggingface.co/docs/transformers/index">Transformers</a>, and <a href="https://huggingface.co/tasks">Tasks</a></li> <li><a href="https://www.langchain.com/">LangChain</a> - the most recent cool kid on the block for LLM workflow chains</li> <li><a href="https://scikit-learn.org/stable/">Scikit-Learn</a>, <a href="https://pytorch.org/">PyTorch</a>, <a href="https://www.tensorflow.org/">TensorFlow</a>, <a href="https://keras.io/">Keras</a>, <a href="https://numpy.org/">Numpy</a> and <a href="https://pandas.pydata.org/">Pandas</a> - python ML libraries</li> <li><a href="https://github.com/elixir-nx/nx">Nx</a>, <a href="https://github.com/elixir-nx/scholar">Scholar</a>, <a href="https://github.com/elixir-nx/axon">Axon</a>, <a href="https://hexdocs.pm/bumblebee/Bumblebee.html">Bumblebee</a>, - elixir ML libraries</li> <li><a href="https://docs.jupyter.org/en/latest/">Jupyter labs</a> - interactive python notebooks</li> <li><a href="https://livebook.dev/">Livebook</a> - interactive elixir notebooks</li> <li><a href="https://aws.amazon.com/sagemaker/">Sagemaker</a> - AWS build/train/deploy ML</li> <li><a href="https://www.trychroma.com/">ChromaDB</a> - open source vector database</li> <li><a href="https://www.llamaindex.ai/">LLama Index</a> - open source data and LLM framework</li> <li><a href="https://github.com/ggerganov/llama.cpp">Llama.cpp</a> - run LLama on local machine</li> <li><a href="https://github.com/Hannibal046/Awesome-LLM">Awesome-LLM</a> - lists more tools</li> <li><a href="https://github.com/awesomedata/awesome-public-datasets#naturallanguage">Awesome-Public-Datasets/NLP</a> - public NLP datasets</li> </ul> <h2 id="training-courses">Training Courses</h2> <p>There are many ML training courses available for free. I found the short courses from deeplearning.ai to be excellent starting points, and their more in-depth &ldquo;Generative AI with LLMs&rdquo; course delves into further detail. The &ldquo;Practical Deep Learning&rdquo; course from fast.ai is outstanding but requires a longer time commitment as it is lengthy and detailed.</p> <ul> <li>DeepLearning.AI - <a href="https://www.coursera.org/learn/generative-ai-with-llms/home/week/1">Generative AI with LLMs</a> (longer course on coursera)</li> <li>DeepLearning.AI - <a href="https://learn.deeplearning.ai/chatgpt-building-system/lesson/1/introduction">Building with ChatGPT API</a> (short course)</li> <li>DeepLearning.AI - <a href="https://learn.deeplearning.ai/chatgpt-prompt-eng/lesson/1/introduction">ChatGPT Prompt Engineering</a> (short course)</li> <li>DeepLearning.AI - <a href="https://learn.deeplearning.ai/langchain-chat-with-your-data/lesson/1/introduction">LangChain chat with Data</a> (short course)</li> <li>Fast.AI - <a href="https://course.fast.ai/">Practical Deep Learning</a> (long and detailed course with book)</li> <li>Community - <a href="https://d2l.ai/index.html">Dive into Deep Learning</a></li> </ul> <h2 id="books">Books</h2> <p>&hellip; and of course there are some good old fashioned books&hellip;</p> <ul> <li><a href="https://www.amazon.com/Python-Data-Analysis-Wrangling-Jupyter/dp/109810403X">Python for data analysis</a> - get started with numpy and pandas</li> <li><a href="https://www.amazon.com/Data-Science-Scratch-Principles-Python/dp/1492041130">Data science from scratch</a> - classical data science fundamentals</li> <li><a href="https://www.amazon.com/Learning-Python-Second-Fran%C3%A7ois-Chollet/dp/1617296864">Deep learning with python</a> - neural networks using TensorFlow and Keras</li> <li><a href="https://www.amazon.com/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1491962291">Hands on machine learning</a> - classic machine learning (plus deep) with scikit</li> <li><a href="https://www.amazon.com/Hundred-Page-Machine-Learning-Book/dp/1777005477">100 page machine learning book</a> - overview of machine learning techniques</li> <li><a href="https://pragprog.com/titles/smelixir/machine-learning-in-elixir/">Machine learning in Elixir</a> - yes you can do ML in a language other than Python!</li> </ul> <h2 id="papers">Papers</h2> <p>The ML community is a scientific, open, and sharing community, and almost all the key insights can be found in the scientific papers shared on arxiv.org. Due to the current excitement, new papers appear almost daily. Here are some of the ones I&rsquo;ve found most insightful:</p> <ul> <li>Jul 2023 - <a href="https://arxiv.org/pdf/2307.10169.pdf">Challenges and applications of LLMs</a></li> <li>Jul 2023 - <a href="https://arxiv.org/pdf/2307.09288.pdf">LLaMA 2: Open foundation and fine tuned chat models</a></li> <li>Jun 2023 - <a href="https://arxiv.org/pdf/2303.18223.pdf">A survey of LLMs</a></li> <li>Jun 2023 - <a href="https://arxiv.org/pdf/2211.05100.pdf">BLOOM - A 176B parameter multilingual LLM</a></li> <li>May 2023 - <a href="https://arxiv.org/pdf/2305.16291.pdf">Voyager - an open ended agent for LLMs</a></li> <li>May 2023 - <a href="https://arxiv.org/pdf/2305.13245.pdf">Google - GQA - Grouped Query Attention</a></li> <li>May 2023 - <a href="https://arxiv.org/pdf/2305.14314.pdf">QLoRA - UW - Efficient finetuning of quantized LLMs</a></li> <li>May 2023 - <a href="https://arxiv.org/pdf/2305.10601.pdf">Google - Tree of Thought: Problem solving with LLMs</a></li> <li>May 2023 - <a href="https://arxiv.org/pdf/2303.17564.pdf">Bloomberg GPT - An LLM for finance</a></li> <li>May 2023 - <a href="https://arxiv.org/pdf/2305.10601.pdf">Tree of Thought - Problem solving with LLMs</a></li> <li>Apr 2023 - <a href="https://arxiv.org/pdf/2304.13712.pdf">Harnessing the power of LLMs in practice</a></li> <li>Mar 2023 - <a href="https://arxiv.org/pdf/2303.15647.pdf">A guide to Parameter efficient fine tuning</a></li> <li>Mar 2023 - <a href="https://arxiv.org/pdf/2210.03629.pdf">Google - ReAct: reasoning and acting in LLMs</a></li> <li>Feb 2023 - <a href="https://arxiv.org/pdf/2302.13971.pdf">LLaMa - Open and efficient foundation language models</a></li> <li>Jan 2023 - <a href="https://arxiv.org/pdf/2211.10435.pdf">PAL - Program aided language models</a></li> <li>Jan 2023 - <a href="https://arxiv.org/pdf/2201.11903.pdf">Google - Chain of thought prompting</a></li> <li>Jan 2023 - <a href="https://arxiv.org/pdf/2205.11916.pdf">Google - LLMs are zero-shot reasoners</a></li> <li>Dec 2022 - <a href="https://arxiv.org/pdf/2210.11416.pdf">Google - Scaling instruction finetuned LLMs</a></li> <li>Nov 2022 - <a href="https://arxiv.org/pdf/2211.15583.pdf">Effectiveness of parameter efficient fine tuning</a></li> <li>Mar 2022 - <a href="https://arxiv.org/pdf/2203.15556.pdf">Chinchilla - Training compute-optimal LLMs</a></li> <li>Mar 2022 - <a href="https://arxiv.org/pdf/2203.02155.pdf">OpenAI - Training to follow instructions with human feedback</a></li> <li>Feb 2022 - <a href="https://arxiv.org/pdf/2009.01325.pdf">OpenAI - Learning to summarize with human feedback</a></li> <li>Oct 2021 - <a href="https://arxiv.org/pdf/2106.09685.pdf">Microsoft - LoRA: Low-Rank adaptation of LLMs</a></li> <li>Sep 2021 - <a href="https://arxiv.org/pdf/2104.08691.pdf">Google - The power of scale for PEFT prompt tuning</a></li> <li>Apr 2021 - <a href="https://arxiv.org/pdf/2005.11401.pdf">Facebook - Retrieval Augmented Generation (RAG)</a></li> <li>Jul 2020 - <a href="https://arxiv.org/pdf/2005.14165.pdf">OpenAI - LLMs are few shot learners</a></li> <li>Feb 2020 - <a href="https://arxiv.org/pdf/2002.04688.pdf">Fast AI - A layered API for deep learning</a></li> <li>Jan 2020 - <a href="https://arxiv.org/pdf/2001.08361.pdf">OpenAI - Scaling Laws for neural language models</a></li> <li>Jan 2018 - <a href="https://arxiv.org/pdf/1801.06146.pdf">UMLFit - language model fine tuning for text classification</a></li> <li>Dec 2017 - <a href="https://arxiv.org/pdf/1706.03762.pdf">Transformers - Attention is all you need (2017)</a></li> </ul> <h2 id="blog-articles">Blog Articles</h2> <p>&hellip; and of course there are an infinite number of blog articles, but here are some of the ones I&rsquo;ve found helpful:</p> <ul> <li><a href="https://willthompson.name/what-we-know-about-llms-primer">What we know about LLMs</a></li> <li><a href="https://www.understandingai.org/p/large-language-models-explained-with">Large language models, explained with a minimum of math and jargon</a></li> <li><a href="https://www.deeplearning.ai/resources/natural-language-processing/">A complete guide to natural language processing</a></li> <li><a href="https://www.oneusefulthing.org/p/how-to-use-ai-to-do-stuff-an-opinionated">How to use AI to do stuff</a></li> <li><a href="https://dockyard.com/blog/2023/05/16/open-source-elixir-alternatives-to-chatgpt">Open source alternatives to ChatGPT</a></li> <li><a href="https://radekosmulski.com/how-to-build-a-deep-learning-system-that-will-answer-questions-about-the-harry-potter-universe/">How to build an LLM to answer questions about the Harry Potter universe</a></li> <li><a href="https://simonwillison.net/2023/Jan/13/semantic-search-answers/">How to implement Q&amp;A against your documentation with GTP</a></li> <li><a href="https://openai.com/research/summarizing-books">Summarizing Books</a></li> <li><a href="https://towardsdatascience.com/an-introduction-to-openai-function-calling-e47e7cd7680e">An introduction to OpenAI function calling</a></li> <li><a href="https://thenewstack.io/langchain-the-trendiest-web-framework-of-2023-thanks-to-ai/">LangChain: The trendiest web framework of 2023 thanks to AI</a></li> <li><a href="https://openbb.co/blog/breaking-barriers-with-openbb-and-llamaIndex">Using LLamaIndex to get GPT to turn a NL query into a (CLI) command</a></li> <li><a href="https://adamloving.com/2023/06/21/edgar-software-agent/?trk=feed-detail_main-feed-card_feed-article-content">Edgar: A voyager inspired software agent</a></li> <li><a href="https://www.oneusefulthing.org/p/it-is-starting-to-get-strange">It’s starting to get strange - ChatGPT with Code Interpreter</a></li> <li><a href="https://www.interconnects.ai/p/llama-2-from-meta">Llama 2: an incredible open source LLM</a></li> <li><a href="https://huggingface.co/blog/llama2">Llama 2 is here</a></li> <li><a href="https://huggingface.co/blog/llama2">Llama 2 on HuggingFace</a></li> <li><a href="https://www.mosaicml.com/blog/mpt-7b">Introducing MPT-7B</a></li> <li><a href="https://huggingface.co/blog/starcoder">Star Coder: a state of the art LLM for code</a></li> <li><a href="https://blog.allenai.org/using-large-language-models-with-care-eeb17b0aed27">Using large language models with care</a></li> <li><a href="https://www.erichgrunewald.com/posts/against-llm-reductionism/">Against LLM reductionism</a></li> <li><a href="https://www.assemblyai.com/blog/emergent-abilities-of-large-language-models/">Emergent abilities of LLMs</a></li> <li><a href="http://jalammar.github.io/illustrated-transformer/">The illustrated transformer</a></li> <li><a href="https://wandb.ai/capecape/LLMs/reports/A-Guide-on-Running-LLMs-Locally--Vmlldzo0Njg5NzMx">How to run LLMs locally</a></li> </ul> <h2 id="conclusion">Conclusion</h2> <p>So, phew, that&rsquo;s a wrap. This article ended up much longer than I expected, but it still really just scratches the surface of where we might be going with LLM development. There are many established and useful tools in the wider ML ecosystem, and the current focus on LLMs and NLP is exciting to see. Personally, I don&rsquo;t really consider these techniques as &ldquo;intelligence&rdquo; as we might intuitively understand the term, and there is <strong>significant work required</strong> to make an LLM useful in a robust production environment&hellip; but they can be very powerful tools for understanding semantic language. As such they can be used - with the right training and orchestration - to open up new solutions to complex problems.</p> <p>I think it&rsquo;s going to be pretty exciting to see if these models can live up to their hype and become another tool in our developer toolbox.</p> Testing Dates in Elixir https://codeincomplete.com/articles/testing-dates-in-elixir/ Wed, 19 Aug 2020 00:00:00 +0000 jake@codeincomplete.com (Jake Gordon) https://codeincomplete.com/articles/testing-dates-in-elixir/ <p>I recently worked on an Elixir/Phoenix project with some complex date processing logic and needed to test the behaviour in a simple and predictable way. Unfortunately any code that directly uses <code>DateTime.utc_now</code> will generate different results every time we run the test suite and make it hard to <code>assert</code> the correct results.</p> <p>For example, consider the following trivial example:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">Event</span> </span></span><span class="line"><span class="cl"> <span class="kd">defstruct</span> <span class="p">[</span> <span class="ss">:type</span><span class="p">,</span> <span class="ss">:occurred_on</span> <span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">new</span><span class="p">(</span><span class="n">type</span><span class="p">)</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="p">%</span><span class="nc">Event</span><span class="p">{</span> <span class="ss">type</span><span class="p">:</span> <span class="n">type</span><span class="p">,</span> <span class="ss">occurred_on</span><span class="p">:</span> <span class="nc">DateTime</span><span class="o">.</span><span class="n">utc_now</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="n">test</span> <span class="s2">&#34;event occurs now&#34;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="n">event</span> <span class="o">=</span> <span class="nc">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:signup</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">assert</span> <span class="n">event</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="ss">:signup</span> </span></span><span class="line"><span class="cl"> <span class="n">assert</span> <span class="n">event</span><span class="o">.</span><span class="n">occurred_on</span> <span class="o">==</span> <span class="nc">DateTime</span><span class="o">.</span><span class="n">utc_now</span> <span class="c1"># BANG!</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><p>This test fails because time progressed between <code>Event.new</code> and the final <code>assert</code>.</p> <p>To resolve this issue we need to be able to mock <code>DateTime.now</code> to always return a fixed, known time. It would also be nice to provide convient time travel methods to travel forward and backward in time while testing. The simplest way to achieve this is to ensure that any code that needs access to the current time uses a proxy <code>Clock</code> module instead of using <code>DateTime.now</code> directly.</p> <p>We start with a basic module that forwards the call directly&hellip;</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">MyApp.Clock</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">utc_now</span> </span></span><span class="line"><span class="cl"> <span class="nc">DateTime</span><span class="o">.</span><span class="n">utc_now</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><p>For the remainder of this article we will incrementally enhance this module to enable mocking the time returned, as well as adding time travel helper methods and more&hellip;</p> <h2 id="a-simple-mockable-clock">A Simple Mockable Clock</h2> <p>If we want to <code>freeze</code> the current time then the first decision we have to make is where should we store that ephemeral information? Each test must be able to freeze time independently, so we can&rsquo;t use a global <code>Agent</code> or <code>GenServer</code>. Luckily the Elixir testing framework - <code>ExUnit</code> - runs each individual test as it&rsquo;s own process so we already have the perfect place - the <a href="https://hexdocs.pm/elixir/Process.html">Process dictionary</a>. The process dictionary is an in-memory key/value store that is unique to the current process. Think of it a little bit like thread local storage in other languages.</p> <p>Let&rsquo;s use the process dictionary to add the ability to freeze and unfreeze the current time:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">MyApp.Clock</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">utc_now</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nc">Process</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="ss">:mock_utc_now</span><span class="p">)</span> <span class="o">||</span> <span class="nc">DateTime</span><span class="o">.</span><span class="n">utc_now</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">freeze</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nc">Process</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="ss">:mock_utc_now</span><span class="p">,</span> <span class="n">utc_now</span><span class="p">())</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">freeze</span><span class="p">(%</span><span class="nc">DateTime</span><span class="p">{}</span> <span class="o">=</span> <span class="n">on</span><span class="p">)</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nc">Process</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="ss">:mock_utc_now</span><span class="p">,</span> <span class="n">on</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">unfreeze</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nc">Process</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="ss">:mock_utc_now</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><h2 id="basic-usage">Basic Usage</h2> <p>Using our <code>Clock</code> module we can update the trivial example from earlier:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">Event</span> </span></span><span class="line"><span class="cl"> <span class="kd">defstruct</span> <span class="p">[</span> <span class="ss">:type</span><span class="p">,</span> <span class="ss">:occurred_on</span> <span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">new</span><span class="p">(</span><span class="n">type</span><span class="p">)</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="p">%</span><span class="nc">Event</span><span class="p">{</span> <span class="ss">type</span><span class="p">:</span> <span class="n">type</span><span class="p">,</span> <span class="ss">occurred_on</span><span class="p">:</span> <span class="nc">Clock</span><span class="o">.</span><span class="n">utc_now</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="n">test</span> <span class="s2">&#34;event occurs now&#34;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nc">Clock</span><span class="o">.</span><span class="n">freeze</span> </span></span><span class="line"><span class="cl"> <span class="n">event</span> <span class="o">=</span> <span class="nc">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:signup</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">assert</span> <span class="n">event</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="ss">:signup</span> </span></span><span class="line"><span class="cl"> <span class="n">assert</span> <span class="n">event</span><span class="o">.</span><span class="n">occurred_on</span> <span class="o">==</span> <span class="nc">Clock</span><span class="o">.</span><span class="n">utc_now</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><p>This time the test will pass because we freeze the time at the start of the test, so the call to <code>Clock.utc_now</code> hidden inside <code>Event.new</code> will return the exact same value as that returned in the final <code>assert</code>.</p> <p>As well as simply freezing the current time in order to stabilize the test, we can go further and freeze the clock at any specific moment in time. Imagine if our example <code>Event</code> module had an <code>occurred_on_label</code> to display the time as a string. We could test this method in a predictable way with an explicit date:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="n">test</span> <span class="s2">&#34;event.occurred_on_label&#34;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nc">Clock</span><span class="o">.</span><span class="n">freeze</span> <span class="sx">~U[2020-01-01 10:20:30Z]</span> </span></span><span class="line"><span class="cl"> <span class="n">event</span> <span class="o">=</span> <span class="nc">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:signup</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">assert</span> <span class="nc">Event</span><span class="o">.</span><span class="n">occurred_on_label</span><span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;Jan 1st 2020, 10:20am&#34;</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><p>By substituting <code>DateTime.utc_now</code> with <code>Clock.utc_now</code> throughout, we have gained control over the current time within our test environment.</p> <h2 id="configure-ecto-to-use-our-mockable-clock">Configure Ecto to use our mockable Clock</h2> <p>In addition to our application code, we may be using Ecto for database access. In which case it is quite likely that Ecto is configured to automatically populate <code>created_at</code> and <code>updated_at</code> timestamp fields by calling <code>DateTime.utc_now</code>. Luckily Ecto can be configured to use a custom method when populating these fields. In our schema modules we can configure Ecto to use our new <code>Clock</code> instead:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">MyApp.User</span> </span></span><span class="line"><span class="cl"> <span class="kn">use</span> <span class="nc">Ecto.Schema</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="na">@timestamps_opts</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="ss">autogenerate</span><span class="p">:</span> <span class="p">{</span> <span class="nc">MyApp.Clock</span><span class="p">,</span> <span class="ss">:utc_now</span><span class="p">,</span> <span class="p">[]</span> <span class="p">},</span> </span></span><span class="line"><span class="cl"> <span class="ss">type</span><span class="p">:</span> <span class="ss">:utc_datetime_usec</span> <span class="c1"># use :microsecond precision when storing timestamps</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">schema</span> <span class="s2">&#34;users&#34;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="c1"># ...</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><p>To avoid having to duplicate this configuration in every schema, we might want to create our own base module and update all schemas to <code>use MyApp.Schema</code> instead of <code>use Ecto.Schema</code>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">MyApp.Schema</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="kd">defmacro</span> <span class="n">__using__</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="k">quote</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="kn">use</span> <span class="nc">Ecto.Schema</span> </span></span><span class="line"><span class="cl"> <span class="na">@timestamps_opts</span> <span class="p">[</span> </span></span><span class="line"><span class="cl"> <span class="ss">autogenerate</span><span class="p">:</span> <span class="p">{</span> <span class="nc">MyApp.Clock</span><span class="p">,</span> <span class="ss">:timestamp</span><span class="p">,</span> <span class="p">[]</span> <span class="p">},</span> </span></span><span class="line"><span class="cl"> <span class="ss">type</span><span class="p">:</span> <span class="ss">:utc_datetime_usec</span><span class="p">,</span> </span></span><span class="line"><span class="cl"> <span class="p">]</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">MyApp.User</span> </span></span><span class="line"><span class="cl"> <span class="kn">use</span> <span class="nc">MyApp.Schema</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="n">...</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><h2 id="time-travel">Time Travel</h2> <p>Now that we have our custom <code>Clock</code> module we can extend it further with helpers to allow our tests to run at any point in time:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmacro</span> <span class="n">time_travel</span><span class="p">(</span><span class="n">to</span><span class="p">,</span> <span class="ss">do</span><span class="p">:</span> <span class="n">block</span><span class="p">)</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="k">quote</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="n">previous</span> <span class="o">=</span> <span class="nc">Clock</span><span class="o">.</span><span class="n">utc_now</span> <span class="c1"># save the current time</span> </span></span><span class="line"><span class="cl"> <span class="nc">Clock</span><span class="o">.</span><span class="n">freeze</span><span class="p">(</span><span class="k">unquote</span><span class="p">(</span><span class="n">to</span><span class="p">))</span> <span class="c1"># freeze the clock at the new time</span> </span></span><span class="line"><span class="cl"> <span class="n">result</span> <span class="o">=</span> <span class="k">unquote</span><span class="p">(</span><span class="n">block</span><span class="p">)</span> <span class="c1"># run the test block</span> </span></span><span class="line"><span class="cl"> <span class="nc">Clock</span><span class="o">.</span><span class="n">freeze</span><span class="p">(</span><span class="n">previous</span><span class="p">)</span> <span class="c1"># reset the clock back to the previous time</span> </span></span><span class="line"><span class="cl"> <span class="n">result</span> <span class="c1"># and return the result</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><p>Giving us even more flexibility in our tests&hellip;</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="n">test</span> <span class="s2">&#34;event.new always occurs &#39;now&#39;&#34;</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nc">Clock</span><span class="o">.</span><span class="n">time_travel</span> <span class="sx">~U[2020-01-01 12:30:00Z]</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="n">event</span> <span class="o">=</span> <span class="nc">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:signup</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">assert</span> <span class="n">event</span><span class="o">.</span><span class="n">occurred_on_label</span> <span class="o">==</span> <span class="s2">&#34;Jan 1st 2020, 12:30pm&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="nc">Clock</span><span class="o">.</span><span class="n">time_travel</span> <span class="sx">~U[2020-02-02 12:30:00Z]</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="n">event</span> <span class="o">=</span> <span class="nc">Event</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:signup</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="n">assert</span> <span class="n">event</span><span class="o">.</span><span class="n">occurred_on_label</span> <span class="o">==</span> <span class="s2">&#34;Feb 2nd 2020, 12:30pm&#34;</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><h2 id="a-configurable-mockable-clock">A Configurable Mockable Clock</h2> <p>Finally, if we want to ensure that the <code>Clock</code> is not accidentally manipulated in our production environment then we can hide the freezing and time travel methods behind application configuration in <code>config/test.exs</code></p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="n">config</span> <span class="ss">:up</span><span class="p">,</span> <span class="nc">MyApp.Clock</span><span class="p">,</span> <span class="ss">freezable</span><span class="p">:</span> <span class="ss">:true</span> </span></span></code></pre></div><p>&hellip; and wrap the dangerous methods behind the compile time configuration flag&hellip;</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-elixir" data-lang="elixir"><span class="line"><span class="cl"><span class="kd">defmodule</span> <span class="nc">MyApp.Clock</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="na">@config</span> <span class="nc">Application</span><span class="o">.</span><span class="n">compile_env</span><span class="p">(</span><span class="ss">:app</span><span class="p">,</span> <span class="n">__MODULE__</span><span class="p">)</span> <span class="o">||</span> <span class="p">[]</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="na">@config</span><span class="p">[</span><span class="ss">:freezable</span><span class="p">]</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># ... define freezing and time travel methods from earlier here</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">else</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="c1"># ... otherwise only allow read-only access to the current time</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="kd">def</span> <span class="n">utc_now</span> <span class="k">do</span> </span></span><span class="line"><span class="cl"> <span class="nc">DateTime</span><span class="o">.</span><span class="n">utc_now</span> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">end</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="k">end</span> </span></span></code></pre></div><h2 id="conclusion">Conclusion</h2> <p>In order to write stable and predictable tests for applications that deal with time we need a simple way to control the <em>current</em> time in our tests. This article has shown how to do this for an Elixir project&hellip;</p> <ul> <li>Implement a proxy <code>Clock</code> module to encapsulate access to <code>DateTime.utc_now</code></li> <li>Update application code to use <code>Clock.utc_now</code> instead of <code>DateTime.utc_now</code></li> <li>Configure Ecto to use <code>Clock.utc_now</code> instead of <code>DateTime.utc_now</code></li> <li>Add <code>Clock</code> helper methods for tests to freeze and travel thru time</li> <li>Use the <a href="https://hexdocs.pm/elixir/Process.html">Process dictionary</a> to store the (per-test) frozen time.</li> <li>Write awesome tests!</li> </ul> <p>The <code>Clock</code> module in my recent project includes more powerful time travel test helpers such as <code>Clock.from_now</code> and <code>Clock.ago</code>. It also becomes a very convenient place to provide real run-time helpers for timezone conversion from UTC to LOCAL times - but that&rsquo;s a <a href="https://xkcd.com/1883/">much-more complex</a> conversation for another time :-)</p> Building a Website with Hugo and Netlify https://codeincomplete.com/articles/building-a-website-with-hugo-and-netlify/ Fri, 27 Mar 2020 00:00:00 +0000 jake@codeincomplete.com (Jake Gordon) https://codeincomplete.com/articles/building-a-website-with-hugo-and-netlify/ <p>This website has been dormant for a couple of years while I&rsquo;ve been busy elsewhere, but I&rsquo;m hoping to be more active in 2020. I&rsquo;m resurrecting this site and slowly dipping my toes back into the tech community. I thought it might be valuable to start by writing up a little on how the site itself is made&hellip;</p> <h2 id="table-of-contents">Table of Contents</h2> <ul> <li><a href="#static-websites">Static Websites</a></li> <li><a href="#markdown-content">Markdown Content</a></li> <li><a href="#extending-markdown-with-hugo-shortcodes">Extending Markdown with Hugo Shortcodes</a></li> <li><a href="#page-layout-and-partials">Page Layout and Partials</a></li> <li><a href="#styling-with-sass">Styling with SASS</a></li> <li><a href="#site-map">Site Map</a></li> <li><a href="#hugo-configuration">Hugo Configuration</a></li> <li><a href="#hugo-directory-structure">Hugo Directory Structure</a></li> <li><a href="#hugo-page-bundles">Hugo Page Bundles</a></li> <li><a href="#development-tasks-with-make">Development Tasks with Make</a></li> <li><a href="#hosting-with-netlify">Hosting with Netlify</a></li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="static-websites">Static Websites</h2> <p>Using a full-blown web application framework like Rails, Django, or Express is usually overkill for websites like this one. Far better to keep it simple and use plain HTML hosted via a basic web server. However, that doesn&rsquo;t mean you have to write all of your articles in raw HTML.</p> <ul> <li>Write article content in <a href="https://guides.github.com/features/mastering-markdown/">markdown format</a></li> <li>Build layout(s) with a template engine</li> <li>Use a static site generator to combine them into the final static output</li> <li>Deploy static files to a simple web server</li> </ul> <p>There are many open source static site generators available, in fact you can find a huge list of them maintained at <a href="https://www.staticgen.com/">staticgen.com</a>. This website happily uses <a href="https://gohugo.io/">Hugo</a>.</p> <ul> <li>Blazing fast</li> <li>Markdown format customizable with shortcodes</li> <li>Clear separation between content and layouts</li> <li>Flexible website navigation scheme</li> <li>Built in SASS style support</li> <li>Single executable dependency</li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="markdown-content">Markdown Content</h2> <p>For static websites content is typically written in <a href="https://guides.github.com/features/mastering-markdown/">markdown format</a>:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gh"># This is a title </span></span></span><span class="line"><span class="cl"><span class="gh"></span> </span></span><span class="line"><span class="cl">This is a paragraph of text filled with insightful commentary about </span></span><span class="line"><span class="cl">a really interesting subject that we hope other folks might find useful. </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">*</span> this </span></span><span class="line"><span class="cl"> <span class="k">*</span> is a list </span></span><span class="line"><span class="cl"> <span class="k">*</span> of bullet points </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">We can highlight <span class="gs">**bold text**</span>, as well as <span class="ge">_italics_</span>. We can also </span></span><span class="line"><span class="cl">include [<span class="nt">hyperlinks</span>](<span class="na">http://codeincomplete.com</span>), as well as </span></span><span class="line"><span class="cl">embed an ![](image.png) </span></span></code></pre></div><p>A summary can be extracted (e.g. for the front page) by splitting around a <code>&lt;!--more--&gt;</code> comment:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">This is an introductory paragraph about the topic with a high level overview. </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c">&lt;!--more--&gt;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">... further details about the topic go &#34;below the fold&#34;. </span></span></code></pre></div><p>Code blocks with syntax highlighting can be included easily:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="s">```javascript </span></span></span><span class="line"><span class="cl"><span class="s"></span> <span class="kd">function</span> <span class="nx">hello</span><span class="p">()</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;hello world&#39;</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="s">```</span></span></span></code></pre></div> <p>The <a href="https://gohugo.io/content-management/front-matter">frontmatter</a> section declares relevant attributes about this content:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">--- </span></span><span class="line"><span class="cl">date: 2011-01-23 # the publish date of this article </span></span><span class="line"><span class="cl">title: Hello World # the title for this article </span></span><span class="line"><span class="cl">slug: welcome # the permalink slug for this article </span></span><span class="line"><span class="cl">--- </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Welcome to my website... </span></span></code></pre></div><p>Read more about markdown:</p> <ul> <li><a href="https://daringfireball.net/projects/markdown/">Daring Fireball: Markdown, John Gruber (Creator of Markdown)</a></li> <li><a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet">Markdown Cheatsheet, Adam Pritchard</a></li> <li><a href="https://www.markdowntutorial.com/">Markdown Tutorial (Interactive), Garen Torikian</a></li> <li><a href="https://www.markdownguide.org/">The Markdown Guide, Matt Cone</a></li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="extending-markdown-with-hugo-shortcodes">Extending Markdown with Hugo Shortcodes</h2> <p>Hugo allows you to go beyond standard markdown in your content by adding <strong>shortcodes</strong>. These are small snippets of HTML that live in the <code>layouts/shortcodes</code> directory and can be inserted into your content. For example, to include a custom horizontal rule you can add a <code>hr.html</code> shortcode:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">hr</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;separator&#39;</span><span class="p">&gt;</span> </span></span></code></pre></div><p>Shortcodes can be used to wrap content by using the go template syntax. For example, to float content to the right you can define a <code>float-right.html</code> shortcode:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">style</span><span class="o">=</span><span class="s">&#39;float:right;&#39;</span><span class="p">&gt;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Inner</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">markdownify</span><span class="w"> </span><span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span></code></pre></div><p>You can even provide a <code>raw.html</code> shortcode to allow raw HTML directly:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="na">.Inner</span><span class="w"> </span><span class="cp">}}</span> </span></span></code></pre></div><p>Shortcodes are inserted into markdown content using the <code>{{&lt; shortcode &gt;}}</code> syntax:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">This is a paragraph of text, followed by a horizontal rule </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">{{<span class="p">&lt;</span> <span class="nt">hr</span> <span class="p">&gt;</span>}} </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">{{<span class="p">&lt;</span> <span class="nt">float-right</span> <span class="p">&gt;</span>}} </span></span><span class="line"><span class="cl"><span class="gu">## This title will float to the right </span></span></span><span class="line"><span class="cl"><span class="gu"></span>{{<span class="p">&lt;</span> <span class="p">/</span><span class="nt">float-right</span> <span class="p">&gt;</span>}} </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">Finally, we can include some raw HTML... </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl">{{<span class="p">&lt;</span> <span class="nt">raw</span> <span class="p">&gt;</span>}} </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">table</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>foo<span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>bar<span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>baz<span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">table</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl">{{<span class="p">&lt;</span> <span class="p">/</span><span class="nt">raw</span> <span class="p">&gt;</span>}} </span></span></code></pre></div><p>Read more about <a href="https://gohugo.io/content-management/shortcodes/">Hugo shortcodes</a></p> <div class='clear'></div> <hr class='separator'> <h2 id="page-layout-and-partials">Page Layout and Partials</h2> <p>The easiest way to present markdown content is with a predefined <a href="https://themes.gohugo.io/">Hugo Theme</a> where you can choose from a variety of styles that suit your need. However, for this website I chose to build my own simple theme with a custom single page layout and re-usable partial HTML snippets:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">layouts/ </span></span><span class="line"><span class="cl"> _default/ </span></span><span class="line"><span class="cl"> single.html <span class="c1"># the default single page layout</span> </span></span><span class="line"><span class="cl"> partials/ </span></span><span class="line"><span class="cl"> head.html <span class="c1"># a partial &lt;head&gt; snippet</span> </span></span><span class="line"><span class="cl"> header.html <span class="c1"># a partial header snippet</span> </span></span><span class="line"><span class="cl"> footer.html <span class="c1"># a partial footer snippet</span> </span></span></code></pre></div><p>The default single page layout is a <a href="https://gohugo.io/templates/">Hugo Template</a> that defines the overall page structure:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;head.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;frame&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="cp">{{-</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;header.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;content&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Type</span><span class="w"> </span><span class="cp">}}</span><span class="s"> </span><span class="cp">{{</span><span class="w"> </span><span class="na">.Slug</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="cp">{{-</span><span class="w"> </span><span class="na">.Content</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="cp">{{-</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;footer.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span> </span></span></code></pre></div><p>The <code>head.html</code> partial defines the <code>&lt;head&gt;</code> section of every page in the site, including:</p> <ul> <li><code>&lt;meta&gt;</code> tags for <code>env</code>, <code>author</code>, <code>deploy-date</code>, <code>publish-date</code> and <code>last-modified-date</code></li> <li>a <code>&lt;title&gt;</code> tag provided by the <code>.Title</code> attribute from the article&rsquo;s frontmatter</li> <li>a <code>&lt;link&gt;</code> tag for the css resource that is minified and fingerprinted by the hugo asset pipeline</li> <li>a <code>&lt;link&gt;</code> tag for the favicon resource that is fingerprinted by the hugo asset pipeline</li> </ul> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$favicon</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resources</span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;images/favicon.ico&#34;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">fingerprint</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$style</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resources</span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;css/content.scss&#34;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">toCSS</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">minify</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">fingerprint</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;en-us&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;utf-8&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">http-equiv</span><span class="o">=</span><span class="s">&#34;x-ua-compatible&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;ie=edge&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;viewport&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;width=device-width, initial-scale=1&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;env&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">hugo</span><span class="na">.Environment</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;deploy-date&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="na">.Format</span><span class="w"> </span><span class="s">&#34;2006-01-02&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;publish-date&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Date.Format</span><span class="w"> </span><span class="s">&#34;2006-01-02&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;last-modified-date&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Lastmod.Format</span><span class="w"> </span><span class="s">&#34;2006-01-02&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;author&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">site</span><span class="na">.Author.name</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span> <span class="cp">{{</span><span class="w"> </span><span class="na">.Title</span><span class="w"> </span><span class="cp">}}</span> | <span class="cp">{{</span><span class="w"> </span><span class="na">.Site.Title</span><span class="w"> </span><span class="cp">}}</span> <span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$style</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;shortcut icon&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$favicon</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span> </span></span></code></pre></div><p>The <code>header.html</code> partial defines the logo and header navigation section, including:</p> <ul> <li>a brand logo fingerprinted by the hugo asset pipeline</li> <li>primary navigation links</li> </ul> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#39;header&#39;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="cp">{{-</span><span class="w"> </span><span class="nx">$brand</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resources</span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;images/brand.png&#34;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">fingerprint</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/&#39;</span><span class="p">&gt;&lt;</span><span class="nt">img</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;brand&#39;</span> <span class="na">src</span><span class="o">=</span><span class="s">&#39;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$brand</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#39;</span> <span class="na">alt</span><span class="o">=</span><span class="s">&#39;go to home page&#39;</span><span class="p">&gt;&lt;/</span><span class="nt">img</span><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;byline&#39;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;links&#39;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/&#39;</span><span class="p">&gt;</span>home<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/articles/&#39;</span><span class="p">&gt;</span>articles<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/games/&#39;</span><span class="p">&gt;</span>games<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/projects/&#39;</span><span class="p">&gt;</span>projects<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/about/&#39;</span><span class="p">&gt;</span>about<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;show-sm-up&#39;</span><span class="p">&gt;</span>| <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/about/resume.pdf&#39;</span><span class="p">&gt;</span>resume<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;contact&#39;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;https://www.linkedin.com/in/jakesgordon&#39;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;link linkedin&#39;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#39;Jake Gordon on LinkeIn&#39;</span><span class="p">&gt;&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;fab fa-linkedin&#39;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;https://twitter.com/jakesgordon&#39;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;link twitter&#39;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#39;@jakesgordon on Twitter&#39;</span><span class="p">&gt;&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;fab fa-twitter&#39;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;https://github.com/jakesgordon&#39;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;link github&#39;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#39;@jakesgordon on GitHub&#39;</span><span class="p">&gt;&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;fab fa-github&#39;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;mailto:jake@codeincomplete.com&#39;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;link email&#39;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#39;email jake@codeincomplete.com&#39;</span><span class="p">&gt;&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;fal fa-envelope&#39;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span></code></pre></div><p>Finally, the <code>footer.html</code> partial defines the footer shown at the bottom of every page:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#39;footer&#39;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;links&#39;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/&#39;</span><span class="p">&gt;</span>home<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/articles/&#39;</span><span class="p">&gt;</span>articles<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/games/&#39;</span><span class="p">&gt;</span>games<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/projects/&#39;</span><span class="p">&gt;</span>projects<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> | </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#39;/about/&#39;</span><span class="p">&gt;</span>about<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#39;copyright&#39;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="ni">&amp;copy;</span> 2011-<span class="cp">{{</span><span class="w"> </span><span class="nx">now</span><span class="na">.Format</span><span class="w"> </span><span class="s">&#34;2006&#34;</span><span class="w"> </span><span class="cp">}}</span> Jake Gordon </span></span><span class="line"><span class="cl"> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </span></span></code></pre></div><p>Read more about <a href="https://gohugo.io/templates/">Hugo templates</a></p> <div class='clear'></div> <hr class='separator'> <h2 id="styling-with-sass">Styling with SASS</h2> <p>This website is fairly basic and doesn&rsquo;t need a lot of styling resources, but it&rsquo;s still much easier to use the <a href="https://sass-lang.com/">SASS</a> precompiler than write raw css directly. Luckily by <a href="https://gohugo.io/getting-started/installing/">installing the extended version of Hugo</a> we get SASS precompiler support built-in with no additional work necessary!</p> <p>In fact you&rsquo;ve already seen where this occurs in the <code>head.html</code> partial:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$style</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resources</span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;css/content.scss&#34;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">toCSS</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">minify</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">fingerprint</span><span class="w"> </span><span class="cp">-}}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;en-us&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"> <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$style</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span> </span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span> </span></span></code></pre></div><p>The <code>$style</code> variable is loaded from <code>css/content.scss</code> and passed through a 3 step <a href="https://gohugo.io/hugo-pipes/introduction/">Hugo Pipeline</a>:</p> <ul> <li><code>toCSS</code> - runs through the SASS precompiler</li> <li><code>minify</code> - minifies the resulting css source</li> <li><code>fingerprint</code> - adds a cache-busting fingerprint to the output file location</li> </ul> <p>The <code>{{ $style.RelPermalink }}</code> attribute is used as the stylesheet <code>href</code>.</p> <p>With a SASS-enabled pipeline in place we can use scss syntax in our styles, for example:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="c1">// we can use SASS variables... </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nn">#frame</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">min-width</span><span class="o">:</span> <span class="nv">$screen-xs</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">max-width</span><span class="o">:</span> <span class="nv">$screen-md</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">margin</span><span class="o">:</span> <span class="mi">0</span> <span class="ni">auto</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">padding</span><span class="o">:</span> <span class="mi">0</span> <span class="mi">2</span><span class="kt">rem</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">// we can use SASS nested styles... </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nn">#footer</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="na">margin</span><span class="o">:</span> <span class="mi">10</span><span class="kt">rem</span> <span class="mi">0</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="na">text-align</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="nc">.copyright</span> <span class="p">{</span> <span class="na">color</span><span class="o">:</span> <span class="nv">$gray-text</span><span class="p">;</span> <span class="na">margin-top</span><span class="o">:</span> <span class="mi">0</span><span class="mf">.5</span><span class="kt">rem</span><span class="p">;</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="nc">.links</span> <span class="p">{</span> <span class="nc">.selected</span> <span class="p">{</span> <span class="na">font-weight</span><span class="o">:</span> <span class="ni">bold</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">// we can use SASS mixins... </span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nn">#promo</span> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">@include</span><span class="nd"> h3</span><span class="p">;</span> </span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div><p>Read more about <a href="https://gohugo.io/hugo-pipes/scss-sass/">Hugo&rsquo;s SASS support</a></p> <div class='clear'></div> <hr class='separator'> <h2 id="site-map">Site Map</h2> <p>Hugo will automatically generate a <code>sitemap.xml</code> file for your website. The site map will list all pages contained in the site along with their last modified date so that search engines will know to index them when their content changes. E.g:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;loc&gt;</span>/articles/building-a-website-with-hugo-and-netlify/<span class="nt">&lt;/loc&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;lastmod&gt;</span>2020-03-27T20:02:35-07:00<span class="nt">&lt;/lastmod&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;/url&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;loc&gt;</span>/articles/javascript-state-machine-3-0-released/<span class="nt">&lt;/loc&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;lastmod&gt;</span>2017-06-10T17:02:56-07:00<span class="nt">&lt;/lastmod&gt;</span> </span></span><span class="line"><span class="cl"> <span class="nt">&lt;/url&gt;</span> </span></span><span class="line"><span class="cl"> ... </span></span></code></pre></div><p>The <code>&lt;loc&gt;</code> of each article is <code>{{ .RelPermalink }}</code> - the root relative permalink for that article.</p> <p>The <code>&lt;lastmod&gt;</code> date is a little more tricky&hellip;</p> <ul> <li>it can be inferred from <code>git</code> if <code>enableGitInfo: true</code> is set in your site configuration</li> <li>it can be specified explicitly with <code>lastmod: &lt;date&gt;</code> in the article frontmatter</li> <li>it can fallback to a <code>date</code> or <code>publishDate</code> if found in the article frontmatter</li> </ul> <p>You can customize the date priority order by <a href="https://gohugo.io/getting-started/configuration/#configure-dates">configuring date rules</a>. For this website I simplified the default behavior with the following custom site configuration:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">frontmatter</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">lastmod</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;lastmod&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;:git&#34;</span><span class="p">]</span><span class="w"> </span></span></span></code></pre></div><ul> <li>an explicit <code>lastmod: &lt;date&gt;</code> in frontmatter always takes priority</li> <li>otherwise use git to infer the last time the content file was modified</li> </ul> <div class='clear'></div> <hr class='separator'> <h2 id="hugo-configuration">Hugo Configuration</h2> <p>The primary <code>config.yaml</code> used for this site includes:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Code inComplete </span><span class="w"> </span><span class="c"># default site &lt;title&gt;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metaDataFormat</span><span class="p">:</span><span class="w"> </span><span class="l">yaml </span><span class="w"> </span><span class="c"># prefer yaml format for content frontmatter</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">enableGitInfo</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># infer lastmod dates from git</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">enableRobotsTXT</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># use my custom robots.txt template</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">author</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Jake Gordon </span><span class="w"> </span><span class="c"># author name</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">email</span><span class="p">:</span><span class="w"> </span><span class="l">jake@codeincomplete.com </span><span class="w"> </span><span class="c"># author email</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">permalinks</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">article</span><span class="p">:</span><span class="w"> </span><span class="l">/articles/:slug </span><span class="w"> </span><span class="c"># permalink pattern for article content</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">game</span><span class="p">:</span><span class="w"> </span><span class="l">/games/:slug </span><span class="w"> </span><span class="c"># permalink pattern for game content</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">frontmatter</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">lastmod</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;lastmod&#34;</span><span class="p">,</span><span class="s2">&#34;:git&#34;</span><span class="p">,</span><span class="s2">&#34;date&#34;</span><span class="p">]</span><span class="w"> </span><span class="c"># allow explicit lastmod to take priority over git inferred value</span><span class="w"> </span></span></span></code></pre></div><p>When building for production with <code>ENV=prod</code> an additional <code>prod/config.yaml</code> is included:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># specify production baseurl when absolute URL&#39;s are required (e.g in RSS feeds)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">baseurl</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://codeincomplete.com/&#34;</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">params</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">assets</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">minify</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># enable asset minification</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">fingerprint</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># enable asset fingerprints</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">social</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># enable &#39;tweet/like this&#39; social buttons</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">disqus</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># enable disqus comments in production</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">analytics</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">google</span><span class="p">:</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="c"># enable google analytics in production</span><span class="w"> </span></span></span></code></pre></div><p>Read more about <a href="https://gohugo.io/getting-started/configuration/">Hugo configuration</a></p> <div class='clear'></div> <hr class='separator'> <h2 id="hugo-directory-structure">Hugo Directory Structure</h2> <p>Hugo projects share a similar directory structure, mine looks something like this:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">assets/ <span class="c1"># assets (css, images) go through the hugo asset pipeline (minification, fingerprinting, etc)</span> </span></span><span class="line"><span class="cl">config/ <span class="c1"># hugo configuration files</span> </span></span><span class="line"><span class="cl">content/ <span class="c1"># website content</span> </span></span><span class="line"><span class="cl"> about/ <span class="c1"># the about page</span> </span></span><span class="line"><span class="cl"> article/ <span class="c1"># the blog articles</span> </span></span><span class="line"><span class="cl"> game/ <span class="c1"># the games page</span> </span></span><span class="line"><span class="cl"> projects/ <span class="c1"># the projects page</span> </span></span><span class="line"><span class="cl"> _index.md <span class="c1"># the home page</span> </span></span><span class="line"><span class="cl">layouts/ <span class="c1"># layout template files</span> </span></span><span class="line"><span class="cl">static/ <span class="c1"># static files that are copied as-is</span> </span></span></code></pre></div><p>Read more about the <a href="https://gohugo.io/getting-started/directory-structure/">Hugo directory structure</a></p> <div class='clear'></div> <hr class='separator'> <h2 id="hugo-page-bundles">Hugo Page Bundles</h2> <p>One of the slightly confusing aspects of Hugo is it&rsquo;s <strong>Page Bundle</strong> metaphor requiring you to understand the difference between:</p> <ul> <li>a <strong>Leaf Bundle</strong> - which contains an <code>index.md</code> file</li> <li>a <strong>Branch Bundle</strong> - which contains an <code>_index.md</code> file</li> </ul> <p>The layout template used to transform the page content will differ:</p> <ul> <li><strong>Leaf Bundle</strong> - expected to have <strong>no</strong> children and transformed using a <em>single page template</em></li> <li><strong>Branch Bundle</strong> - expected to have child bundles and transformed using a <em>list template</em></li> </ul> <p>The transformation of other content (e.g. markdown files) found in the same directory will differ:</p> <ul> <li><strong>Leaf Bundle</strong> - no further content transformation will occur in that directory</li> <li><strong>Branch Bundle</strong> - additional content will continue to be transformed</li> </ul> <p>The handling of assets (e.g. images) found in the same directory will differ:</p> <ul> <li><strong>Leaf Bundle</strong> - all assets in that directory, or any sub directory, will be copied</li> <li><strong>Branch Bundle</strong> - only assets found in that directory will be copied</li> </ul> <p>The handling of permalinks will differ:</p> <ul> <li><strong>Leaf Bundle</strong> - will respect any <code>permalinks</code> configuration</li> <li><strong>Branch Bundle</strong> - do not respect <code>permalinks</code> config, so may require a custom <code>url</code></li> </ul> <p>Read more about <a href="https://gohugo.io/content-management/page-bundles/">Hugo&rsquo;s page bundles</a>.</p> <blockquote> <p><em>NOTE: Personally, I think the page bundle metaphor and the single vs list page template is an unnecessary complexity for Hugo that seems caused by it&rsquo;s attempts to automagically generate category/taxonomy pages for you - a feature that seems to cause more problems than the value it provides. However, despite this complexity Hugo is still a fantastic tool!</em></p> </blockquote> <div class='clear'></div> <hr class='separator'> <h2 id="development-tasks-with-make">Development Tasks with Make</h2> <p>We only need 2 commands for day-to-day development with Hugo:</p> <ul> <li><strong>run</strong> - use <code>hugo server --watch</code> to rebuild and reload when content changes</li> <li><strong>build</strong> - use <code>hugo</code> to build final production output</li> </ul> <p>We can use a simple <code>Makefile</code> to encapsulate those 2 tasks:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-make" data-lang="make"><span class="line"><span class="cl"><span class="nv">ENV</span> <span class="o">?=</span> dev </span></span><span class="line"><span class="cl"><span class="nv">PORT</span> <span class="o">?=</span> <span class="m">3000</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nf">run</span><span class="o">:</span> </span></span><span class="line"><span class="cl"> hugo server --watch --environment <span class="k">$(</span>ENV<span class="k">)</span> --config config/config.yaml --port<span class="o">=</span><span class="k">$(</span>PORT<span class="k">)</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="nf">build</span><span class="o">:</span> </span></span><span class="line"><span class="cl"> hugo --environment <span class="k">$(</span>ENV<span class="k">)</span> --config config/config.yaml --cleanDestinationDir </span></span></code></pre></div><p>Allowing us to easily <code>make run</code> the development server, or <code>ENV=prod make build</code> the final output.</p> <div class='clear'></div> <hr class='separator'> <h2 id="hosting-with-netlify">Hosting with Netlify</h2> <p>Once the content is written, the layouts built, and the website is in working order we need to be able to host it somewhere. Previous options include <a href="https://pages.github.com/">Github Pages</a>, an <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html">S3 bucket</a>, or a cheap <a href="https://www.linode.com/products/nanodes/">Linode</a> or <a href="https://www.digitalocean.com/products/droplets/">Digital Ocean</a> virtual server.</p> <p>Luckily there is a fantastic option available with <a href="https://www.netlify.com/">Netlify</a></p> <ul> <li>Designed for hosting static sites</li> <li>Native Hugo support</li> <li>HTTP/2 support</li> <li>Global CDN</li> <li>Free tier</li> <li>Easy to get started</li> <li>Automatic ETag cache invalidation</li> <li>Automatic Gzip support</li> <li>Support for far-future Cache control headers</li> <li>Easy preview or staging deploys from git branches</li> <li>Continuous delivery via github commits</li> <li>Analytics available (in paid tier)</li> </ul> <p>The basic setup process was very easy&hellip;</p> <ul> <li>Register for a new account</li> <li>Create a new site from the github repository</li> <li>Create a new deploy <ul> <li>Set the build command to <code>hugo --environment prod --config config/config.yaml</code></li> <li>Set the publish directory to <code>public/prod</code></li> <li>Set a <code>HUGO_VERSION=0.67.1</code> environment variable</li> </ul> </li> </ul> <p>That was all that was needed to build and deploy the site and make it available within a few minutes at <code>https://codeincomplete.netlify.com</code>. Once the site was deployed, routing DNS to the new location was as simple as replacing my previous <code>A</code> record with a new <code>ALIAS</code> record within my DNS provider (currently I use <a href="https://www.namecheap.com/">NameCheap</a>).</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ALIAS codeincomplete codeincomplete.netlify.com </span></span></code></pre></div><p>Once DNS was configured correctly I clicked the Netlify &ldquo;Provision Let&rsquo;s Encrypt SSL/TLS Certificate&rdquo; button, waited for a few minutes for the provisioning to complete, and the newly deployed website was available and secure at <code>https://codeincomplete.com</code>.</p> <p>Finally, to improve performance further, since I am using the Hugo pipeline to fingerprint my static css and image assets, I added a <code>_headers</code> file to tell Netlify to set far-future cache control headers on my <code>css</code> and <code>images</code> folders:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># far-future for fingerprinted css assets</span> </span></span><span class="line"><span class="cl">/css/* </span></span><span class="line"><span class="cl"> cache-control: max-age<span class="o">=</span><span class="m">31536000</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1"># far-future for fingerprinted image assets</span> </span></span><span class="line"><span class="cl">/images/* </span></span><span class="line"><span class="cl"> cache-control: max-age<span class="o">=</span><span class="m">31536000</span> </span></span></code></pre></div><p>It was a surprisingly simple and smooth process.</p> <p>Great job Netlify!</p> <div class='clear'></div> <hr class='separator'> <h2 id="conclusion">Conclusion</h2> <a class='book pull-right' href='https://pragprog.com/book/bhhugo/build-websites-with-hugo'><img src='https://codeincomplete.com/images/books/building-websites-with-hugo.194cc027017a9a04b5d72ef664986b4290842c1c05d6561f38edd2ec64df1566.png' width='200' height='240' alt='Building Websites with Hugo - Brian P. Hogan' title='Building Websites with Hugo - Brian P. Hogan'></a> <p>This site has been dormant for a while, but the underlying choice to build it as a static website has made it relatively easy to maintain and update over the years. I&rsquo;m fairly happy that the choice - <em>made many years ago</em> - to use Hugo has held up and continues to be maintained as an active and constantly improving tool.</p> <p>If you&rsquo;re interested in learning more about Hugo I highly recommend Brian Hogans new book <a href="https://pragprog.com/book/bhhugo/build-websites-with-hugo ">Building Websites with Hugo</a></p> <p>I&rsquo;m also delighted to recently discover <a href="https://netlify.com">Netlify</a> as a great hosting solution and to finally turn off my Linode virtual machine that has been quietly running without problems for over 5 years - you served us well and will be missed!</p> <br> <h2 id="related-links">Related Links</h2> <ul> <li><a href="https://gohugo.io/">Hugo</a> <ul> <li><a href="https://gohugo.io/content-management/shortcodes/">Hugo shortcodes</a></li> <li><a href="https://gohugo.io/templates/">Hugo templates</a></li> <li><a href="https://gohugo.io/hugo-pipes/scss-sass/">Hugo SASS support</a></li> <li><a href="https://gohugo.io/getting-started/configuration/">Hugo configuration</a></li> <li><a href="https://gohugo.io/getting-started/directory-structure/">Hugo directory structure</a></li> <li><a href="https://gohugo.io/content-management/page-bundles/">Hugo page bundles</a></li> <li><a href="https://gohugo.io/hugo-pipes/introduction/">Hugo Pipeline</a></li> <li><a href="https://pragprog.com/book/bhhugo/build-websites-with-hugo ">Building Websites With Hugo</a></li> </ul> </li> <li><a href="https://www.netlify.com/">Netlify</a></li> <li><a href="https://guides.github.com/features/mastering-markdown/">Markdown Format</a></li> <li><a href="https://sass-lang.com">SASS</a></li> <li><a href="https://fontawesome.com/">Font Awesome</a></li> </ul> Javascript State Machine 3.0 https://codeincomplete.com/articles/javascript-state-machine-3-0-released/ Sat, 10 Jun 2017 00:00:00 +0000 jake@codeincomplete.com (Jake Gordon) https://codeincomplete.com/articles/javascript-state-machine-3-0-released/ <p>Earlier this year, the <a href="https://github.com/jakesgordon/javascript-state-machine">javascript-state-machine</a> libary underwent a major rewrite for version 3.0 in order to support more advanced use cases - as well as tidy up some of the existing ones. It has been available as a pre-release candidate for the last few months and has now been formally released as <strong>v3.0.1</strong></p> <h2 id="installing">Installing</h2> <p>You can install using npm:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> npm install javascript-state-machine </span></span></code></pre></div><h2 id="highlights">Highlights</h2> <ul> <li>Improved Construction.</li> <li>Arbitrary Data and Methods.</li> <li>Observable Transitions</li> <li>Conditional Transitions</li> <li>Promise-based Asynchronous Transitions</li> <li>Improved Transition Lifecycle Events</li> <li>Goto State</li> <li>State History</li> <li>Visualization</li> <li>Webpack build system</li> </ul> <h2 id="documentation">Documentation</h2> <ul> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/upgrading-from-v2.md">WHAT&rsquo;S NEW &amp; Upgrading from v2</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/">README</a></li> </ul> <p>Additional documentation is available on github:</p> <ul> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/states-and-transitions.md">States and Transitions</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/data-and-methods.md">Data and Methods</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/lifecycle-events.md">Lifecycle Events</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/async-transitions.md">Asynchronous Transitions</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/initialization.md">Initialization</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/error-handling.md">Error Handling</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/state-history.md">State History</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/visualization.md">Visualization</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/state-machine-factory.md">State Machine Factory</a></li> </ul> <h2 id="licensing">Licensing</h2> <p>Please note that v3.0 is dual licensed under <a href="http://www.gnu.org/licenses/lgpl-3.0.html">LGPLv3</a> for open source projects, while a <a href="https://github.com/jakesgordon/javascript-state-machine/blob/master/docs/commercial-license.md">commercial license is also available</a>.</p> Javascript State Machine 3.0 RC1 https://codeincomplete.com/articles/javascript-state-machine-v3-0-0-rc-1/ Tue, 10 Jan 2017 00:00:00 +0000 jake@codeincomplete.com (Jake Gordon) https://codeincomplete.com/articles/javascript-state-machine-v3-0-0-rc-1/ <p>The <a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3">javascript-state-machine</a> library is having a major rewrite for version 3.0 in order to support more advanced use cases - as well as tidy up some of the existing ones. It is now available as a pre-release candidate for early adopters.</p> <h2 id="installing">Installing</h2> <p>You can install the release candidate using npm:</p> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"> npm install --save javascript-state-machine@3.0.0-rc.1 </span></span></code></pre></div><h2 id="highlights">Highlights</h2> <ul> <li>Improved Construction.</li> <li>Arbitrary Data and Methods.</li> <li>Observable Transitions</li> <li>Conditional Transitions</li> <li>Promise-based Asynchronous Transitions</li> <li>Improved Transition Lifecycle Events</li> <li>Goto State</li> <li>State History</li> <li>Visualization</li> <li>Webpack build system</li> </ul> <h2 id="documentation">Documentation</h2> <ul> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/upgrading-from-v2.md">WHAT&rsquo;S NEW &amp; Upgrading from v2</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3">README</a></li> </ul> <p>Additional documentation is available on github in the v3 branch:</p> <ul> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/states-and-transitions.md">States and Transitions</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/data-and-methods.md">Data and Methods</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/lifecycle-events.md">Lifecycle Events</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/async-transitions.md">Asynchronous Transitions</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/initialization.md">Initialization</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/error-handling.md">Error Handling</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/state-history.md">State History</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/visualization.md">Visualization</a></li> <li><a href="https://github.com/jakesgordon/javascript-state-machine/tree/v3/docs/state-machine-factory.md">State Machine Factory</a></li> </ul> <h2 id="licensing">Licensing</h2> <p>Please note that v3.0 is dual licensed under <a href="http://www.gnu.org/licenses/lgpl-3.0.html">LGPLv3</a> for open source projects, while a <a href="https://github.com/jakesgordon/javascript-state-machine/blob/v3/docs/commercial-license.md">commercial license is also available</a>.</p>