Skip to content

Instantly share code, notes, and snippets.

@SeanMcP
Created October 20, 2022 11:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SeanMcP/015446a7bcadd2f3debc01af9fd32b5d to your computer and use it in GitHub Desktop.
Save SeanMcP/015446a7bcadd2f3debc01af9fd32b5d to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[https://redd.one RSS Feed]]></title><description><![CDATA[Learn the latest trends in web development, as well as the ageless basics, on this clean ad-free personal blog.]]></description><link>https://redd.one</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 20 Oct 2022 11:08:41 GMT</lastBuildDate><item><title><![CDATA[Practical Guide to Custom Jest Matchers]]></title><description><![CDATA[What is a matcher? In Jest , functions like .toEqual() or .toHaveProperty() are called matchers . While Jest comes with an extensive…]]></description><link>https://redd.one/practical-guide-to-custom-jest-matchers</link><guid isPermaLink="false">https://redd.one/practical-guide-to-custom-jest-matchers</guid><pubDate>Wed, 22 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;what-is-a-matcher&quot;&gt;What is a matcher?&lt;/h2&gt;&lt;p&gt;In &lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt;, functions like &lt;code&gt;.toEqual()&lt;/code&gt; or &lt;code&gt;.toHaveProperty()&lt;/code&gt; are called &lt;em&gt;matchers&lt;/em&gt;. While Jest comes with an extensive amount of default matchers, you can also create your own to encapsulate repetitive assertions in your tests.&lt;/p&gt;&lt;h2 id=&quot;symmetric-and-asymmetric-matchers&quot;&gt;Symmetric and Asymmetric matchers&lt;/h2&gt;&lt;p&gt;There are two types of matchers in Jest: symmetric and asymmetric.&lt;/p&gt;&lt;p&gt;A &lt;em&gt;symmetric matcher&lt;/em&gt; is the one that asserts data in its entirety. In other words, it&amp;#x27;s a strict comparison matcher. Here&amp;#x27;s an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;expect(myObject).toEqual({ id: 123 })
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this assertion, the &lt;code&gt;myObject&lt;/code&gt; must equal to the &lt;code&gt;{ id: 123 }&lt;/code&gt; object. If it doesn&amp;#x27;t have the required &amp;quot;id&amp;quot; property or has additional properties that are not present in the expected object, the assertion will fail. In that regard, this matcher is symmetric because it reflects the expected value in its entirety.&lt;/p&gt;&lt;p&gt;An &lt;em&gt;asymmetric matcher&lt;/em&gt; is a kind of a matcher that asserts data partially. Here&amp;#x27;s the same object assertion as above but using an asymmetric matcher now:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;expect(myObject).toEqual(
expect.objectContaining({
id: 123,
})
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The symmetric &lt;code&gt;.toEqual&lt;/code&gt; matcher remains but you may notice that instead of accepting an object as its argument, it now accepts the call to &lt;code&gt;expect.objectContaining()&lt;/code&gt; function. The latter is the asymmetric matcher, as it describes a subset of properties that must exist on &lt;code&gt;myObject&lt;/code&gt;, ignoring any additional properties.&lt;/p&gt;&lt;h2 id=&quot;creating-a-custom-matcher&quot;&gt;Creating a custom matcher&lt;/h2&gt;&lt;p&gt;Jest provides the &lt;code&gt;expect.extend()&lt;/code&gt; API to implement both custom symmetric and asymmetric matchers. This API accepts an object where keys represent matcher names, and values stand for custom matcher implementations.&lt;/p&gt;&lt;p&gt;Extending the default &lt;code&gt;expect&lt;/code&gt; function can be done as a part of the testing setup. Make sure you have your Jest configuration file created and pointing to the custom setup file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export default {
// Let Jest know that there&amp;#x27;s an additional setup
// before the tests are run (i.e. matcher extensions).
setupFilesAfterEnv: [&amp;#x27;./jest.setup.ts&amp;#x27;],
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s start by implementing a custom symmetric matcher.&lt;/p&gt;&lt;h2 id=&quot;custom-symmetric-matcher&quot;&gt;Custom symmetric matcher&lt;/h2&gt;&lt;p&gt;In our application, we often assert that a number lies within the range of numbers. To reduce the repetition and make tests reflect the intention, let&amp;#x27;s implement a custom &lt;code&gt;.toBeWithinRange()&lt;/code&gt; matcher.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// jest.setup.ts
expect.extend({
toBeWithinRange(actual, min, max) {
if (typeof actual !== &amp;#x27;number&amp;#x27;) {
throw new Error(&amp;#x27;Actual value must be a number&amp;#x27;)
}
const pass = actual &amp;gt;= min &amp;amp;&amp;amp; actual &amp;lt;= max
},
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we&amp;#x27;ve extended the Jest&amp;#x27;s &lt;code&gt;expect&lt;/code&gt; global function with a new function called &lt;code&gt;toBeWithinRange&lt;/code&gt;. Jest will always provide our matchers with the actual data as the first argument, and we can utilize the remaining arguments for the matcher to accept additional input (for example, the allowed range of numbers).&lt;/p&gt;&lt;p&gt;Since anything can be passed to the &lt;code&gt;expect()&lt;/code&gt; in the test run, don&amp;#x27;t forget to check for the &lt;code&gt;actual&lt;/code&gt; type. In this matcher, we ensure that the provided &lt;code&gt;actual&lt;/code&gt; value is a number.&lt;/p&gt;&lt;p&gt;We are checking whether the &lt;code&gt;actual&lt;/code&gt; number is within the &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; range, and writing the result to the &lt;code&gt;pass&lt;/code&gt; variable. Now we need to let Jest know how to respect that variable and mark assertions as passed or failed based on its value.&lt;/p&gt;&lt;p&gt;To do that, custom matchers must return an object of the following shape:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;{
pass: boolean
message(): string
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s do just that:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// jest.setup.ts
expect.extend({
toBeWithinRange(actual, min, max) {
if (typeof actual !== &amp;#x27;number&amp;#x27;) {
throw new Error(&amp;#x27;Actual value must be a number&amp;#x27;)
}
const pass = actual &amp;gt;= min &amp;amp;&amp;amp; actual &amp;lt;= max
return {
pass,
message: pass
? () =&amp;gt; `expected ${actual} not to be within range (${min}..${max})`
: () =&amp;gt; `expected ${actual} to be within range (${min}..${max})`,
}
},
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, whenever our matcher returns &lt;code&gt;{ pass: false }&lt;/code&gt;, the test assertion will fail, and Jest will communicate the failure to us as it usually does.&lt;/p&gt;&lt;p&gt;Notice how we&amp;#x27;re returning a conditional &lt;code&gt;message&lt;/code&gt; value, even if the matcher has passed. That is done due to &lt;em&gt;inverse matches&lt;/em&gt;, with which you are also very likely familiar:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;expect(5).not.toBeWithinRange([3, 5])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For our matcher, &lt;code&gt;5&lt;/code&gt; is indeed within the given range of &lt;code&gt;[3, 5]&lt;/code&gt;, so it will return &lt;code&gt;{ pass: true }&lt;/code&gt;. But it&amp;#x27;s the &lt;code&gt;.not.&lt;/code&gt; chain that makes this assertion inverted, flipping it upside down. Jest knows that inverse matches must return &lt;code&gt;{ pass: false }&lt;/code&gt;, and whenever that&amp;#x27;s not the case, it will print the &lt;code&gt;message&lt;/code&gt; that we&amp;#x27;ve defined for that case. And that is why we still return a message when the matcher passes, and why that message says that &amp;quot;the number must &lt;em&gt;not&lt;/em&gt; be within range&amp;quot;.&lt;/p&gt;&lt;p&gt;The final touch is to let TypeScript know that we&amp;#x27;ve just extended a globally exposed function of a third-party library. To do that, create a &lt;code&gt;jest.d.ts&lt;/code&gt; file and extend &lt;code&gt;jest.Matchers&lt;/code&gt; and &lt;code&gt;jest.ExpectExtendMap&lt;/code&gt; interfaces there:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type OwnMatcher&amp;lt;Params extends unknown[]&amp;gt; = (
this: jest.MatcherContext,
actual: unknown,
...params: Params
) =&amp;gt; jest.CustomMatcherResult
declare global {
namespace jest {
interface Matchers&amp;lt;R, T&amp;gt; {
// Note that we are defining a public call signature
// for our matcher here (how it will be used):
// expect(5).toBeInRange(3, 7)
toBeWithinRange(min: number, max: number): T
}
interface ExpectExtendMap {
// Here, we&amp;#x27;re describing the call signature of our
// matcher for the &amp;quot;expect.extend()&amp;quot; call.
toBeWithinRange: OwnMatcher&amp;lt;[min: number, max: number]&amp;gt;
}
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also, let&amp;#x27;s make sure that this definition is included in &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
&amp;quot;include&amp;quot;: [&amp;quot;jest.d.ts&amp;quot;]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can now use our custom matcher in tests:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;it(&amp;#x27;asserts the number is within range&amp;#x27;, () =&amp;gt; {
expect(5).toBeWithinRange(3, 5) // ✅
expect(3).toBeWithinRange(10, 20) // ❌
})
it(&amp;#x27;asserts the number is not within range&amp;#x27;, () =&amp;gt; {
expect(10).not.toBeWithinRange([3, 5]) // ✅
expect(5).not.toBeWithinRange([1, 10]) // ❌
})
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;custom-asymmetric-matcher&quot;&gt;Custom asymmetric matcher&lt;/h2&gt;&lt;p&gt;Similar to symmetric matchers, asymmetric ones are defined via &lt;code&gt;expect.extend()&lt;/code&gt; in your test setup file.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s create a custom asymmetric matcher that asserts that a given &lt;code&gt;Set&lt;/code&gt; has a subset of values.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// jest.setup.ts
expect.extend({
// ...any other custom matchers.
setContaining(actual, expected) {
if (!(actual instanceof Set)) {
throw new Error(&amp;#x27;Actual value must be a Set&amp;#x27;)
}
const pass = expected.every((item) =&amp;gt; actual.has(item))
return {
pass,
message: pass
? () =&amp;gt; `expected Set not to contain ${expected.join(&amp;#x27;, &amp;#x27;)}`
: () =&amp;gt; `expected Set to contain ${expected.join(&amp;#x27;, &amp;#x27;)}`,
}
},
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since the &lt;code&gt;setContaining&lt;/code&gt; matcher is asymmetric, it should be exposed as &lt;code&gt;expect.setContaining()&lt;/code&gt; and not &lt;code&gt;expect(x).setContaining()&lt;/code&gt;. Let&amp;#x27;s make sure we extend the &lt;code&gt;jest.Expect&lt;/code&gt; type with our asymmetric matcher instead of extending the &lt;code&gt;jest.Matchers&lt;/code&gt; type like we did with &lt;code&gt;toBeWithinRange&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// jest.d.ts
import { MatcherFunction } from &amp;#x27;expect&amp;#x27;
declare global {
namespace jest {
// ...any other extensions, like &amp;quot;Matchers&amp;quot;.
interface Expect {
// Once again, here we describe how our matcher
// will be used in our tests:
// expect.setContaining([&amp;#x27;john&amp;#x27;])
setContaining&amp;lt;T extends unknown&amp;gt;(expected: Set&amp;lt;T&amp;gt;): Set&amp;lt;T&amp;gt;
}
interface ExpectExtendMap {
// Let&amp;#x27;s keep our extension signature type-safe.
setContaining: MatcherFunction&amp;lt;[expected: unknown[]]&amp;gt;
// ...any other matcher definitions.
toBeWithinRange: MatcherFunction&amp;lt;[min: number, max: number]&amp;gt;
}
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once this is done, we can use our custom asymmetric matcher in tests:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;it(&amp;#x27;asserts a subset of the given Set values&amp;#x27;, () =&amp;gt; {
expect({ friends: new Set([&amp;#x27;john&amp;#x27;, &amp;#x27;kate&amp;#x27;]) }).toEqual({
friends: expect.setContaining([&amp;#x27;kate&amp;#x27;]), // ✅
})
// Annotating the actual data will give us type-safety
// down to each individual asymmetric matcher.
interface User {
friends: Set&amp;lt;string&amp;gt;
}
expect(user).toEqual&amp;lt;User&amp;gt;({
friends: expect.setContaining([5]),
// TypeError: &amp;quot;number&amp;quot; is not assignable to type &amp;quot;string&amp;quot;.
})
})
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;You may have noticed that the &lt;code&gt;expect.extend()&lt;/code&gt; part is identical for both types of matchers. In fact, even our asymmetric matcher will be exposed as &lt;code&gt;expect(v).setContaining(subset)&lt;/code&gt; during test runtime. However, to preserve semantic names, I highly recommend describing symmetric matchers by extending &lt;code&gt;jest.Matchers&lt;/code&gt;, and the asymmetric ones by extending &lt;code&gt;jest.Expect&lt;/code&gt; separately. It&amp;#x27;s a type limitation only but it will produce more consistent test matcher semantics.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;And that&amp;#x27;s how you extend Jest with both symmetric and asymmetric matchers while preserving the type-safety of your tests. Custom matchers are certainly not a beginner-friendly topic but they are indispensable when it comes to designing a custom testing vocabulary in somewhat larger projects.&lt;/p&gt;&lt;p&gt;You can browse through the source code from this article in this repository:&lt;/p&gt;&lt;div owner=&quot;kettanaito&quot; repo=&quot;jest-custom-matchers&quot;&gt;&lt;/div&gt;&lt;p&gt;As a cherry on top, here are a few resources that can expand your knowledge about Jest matchers:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://jestjs.io/docs/expect&quot;&gt;&lt;code&gt;expect&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://jestjs.io/blog/2018/05/29/jest-23-blazing-fast-delightful-testing#custom-asymmetric-matchers&quot;&gt;Custom asymmetric matchers&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/facebook/jest/blob/bb0956589f9dbddcc13db0f42b2d4d9ff642b2cd/examples/expect-extend/toBeWithinRange.ts#L11&quot;&gt;Example of &lt;code&gt;.toBeWithinRange()&lt;/code&gt; from Jest&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Thank you for reading! Make sure to &lt;a href=&quot;https://twitter.com/kettanaito&quot;&gt;follow me on Twitter&lt;/a&gt; if you wish to stay in tune when I publish more pieces like this. You can also share this one with your coworkers and friends, I&amp;#x27;d highly appreciate that.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Dark Side of Open Source]]></title><description><![CDATA["Contribute to open source, it's the best thing that's happened to me!" I hear more and more often on Twitter these days. While I don't…]]></description><link>https://redd.one/the-dark-side-of-open-source</link><guid isPermaLink="false">https://redd.one/the-dark-side-of-open-source</guid><pubDate>Mon, 13 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;quot;Contribute to open source, it&amp;#x27;s the best thing that&amp;#x27;s happened to me!&amp;quot; I hear more and more often on Twitter these days. While I don&amp;#x27;t object to the statement itself, I feel that it unintentionally (or intentionally) omits a decent chunk of truth about contributing and authoring open source. It sounds like you&amp;#x27;re recommending a hiking route that, eventually, opens up to an unforgettable view but you fail to mention the number of challenges and the preparation this hike requires. I know there will be people who will take this route and will find themselves overwhelmed, if not unprepared, in the face of what a life of an open source maintainer actually is. As I&amp;#x27;ve created this blog with the purpose to write about things I think people should talk about more often, it&amp;#x27;s time for me to touch the vast and alluring plane of open source.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;status-check&quot;&gt;Status check&lt;/h2&gt;&lt;p&gt;I&amp;#x27;m going to write from an open source author&amp;#x27;s standpoint. Partially, because this is where I have the most experience and can spotlight the issues I&amp;#x27;ve faced myself. But mainly because I believe it&amp;#x27;s a matter of a single commit when a contributor becomes an author.&lt;/p&gt;&lt;p&gt;There is a significant difference between the contributor&amp;#x27;s and the author&amp;#x27;s experience when it comes to open source. Visiting a park on a weekend to do some voluntary maintenance isn&amp;#x27;t quite the same as being that park&amp;#x27;s keeper, cutting grass every day, arranging repairs, nurturing, and treating the animals that dwell in the area. It&amp;#x27;s like living in two different worlds that, eventually, will collide.&lt;/p&gt;&lt;p&gt;I should mention that I have no goal to discourage you from diving into open source and becoming whomever of these two you wish to be. Open source is a great place to learn and make friends—that&amp;#x27;s what you&amp;#x27;ve been told correctly. It&amp;#x27;s been my primary way of learning for half a decade now, and it would be foolish for me to prevent you from learning yourself. So, if you&amp;#x27;re considering landing that first contribution—&lt;strong&gt;do that by all means&lt;/strong&gt;. See you at the end of the article.&lt;/p&gt;&lt;h2 id=&quot;enjoy-being-undiscovered&quot;&gt;Enjoy being undiscovered&lt;/h2&gt;&lt;p&gt;I say this without the slightest hints of sarcasm. Enjoy the time when your projects are just that—yours—for that is truly magical. Once they get discovered and picked up, things will change based on the adoption rate, so don&amp;#x27;t miss out on the fresh air of a new project that nobody knows about.&lt;/p&gt;&lt;p&gt;Here, I&amp;#x27;m tempted to say &amp;quot;build something great and people will follow&amp;quot; but that is very far from the truth. It&amp;#x27;s like making a name in the music industry: it has nothing to do with the genre and quality of music you&amp;#x27;re writing. It&amp;#x27;s a matter of getting lucky once, twice, and then a couple of times more. Because it takes a tremendous amount of effort just to convince people to look at what you&amp;#x27;re building, let alone adopt and contribute to it.&lt;/p&gt;&lt;p&gt;Once again, enjoy being undiscovered. There&amp;#x27;s a unique touch to playing your songs from your garage, maybe even with a band of friends. Not everyone needs to gather big stadiums or sell platinum records. The life of a famous musician is more than hoarding piles of cash and drinking mega pints of wine. There are challenges and struggles, so enjoy while those aren&amp;#x27;t on your daily list yet.&lt;/p&gt;&lt;p&gt;If, in the end, you decide to go all-in and seek attention, employ any tools available to you: social marketing, speaking at conferences, joining existing communities, and making friends on the network. The latter was what worked the best for me, and I&amp;#x27;ve got lucky beyond measure to get my work featured multiple times by incredible people in the community. Your journey, however, is yours to make.&lt;/p&gt;&lt;p&gt;You will learn a lot about marketing while promoting your open source work. Well, no wonder. You won&amp;#x27;t be promoting open source, to be frank, you will be promoting…&lt;/p&gt;&lt;h2 id=&quot;its-a-product-not-a-feature&quot;&gt;It&amp;#x27;s a product, not a feature&lt;/h2&gt;&lt;p&gt;Think about an open source project that associates with &amp;quot;successful&amp;quot; in your mind. Now, what are the chances that this project is:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Acquihired;&lt;/li&gt;&lt;li&gt;Owned by a corporation;&lt;/li&gt;&lt;li&gt;Serving as an entry point to a paid product;&lt;/li&gt;&lt;li&gt;Won a lottery ticket of getting adequate number of voluntary sponsorships;&lt;/li&gt;&lt;li&gt;Any combination of the above.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In the JavaScript ecosystem, the chances of that are roughly around 100%. And if it seems you&amp;#x27;ve thought of an exception, then it&amp;#x27;s surely headed into one of these directions without you knowing.&lt;/p&gt;&lt;div&gt;Open source libraries &lt;u&gt;are&lt;/u&gt; products.&lt;/div&gt;&lt;p&gt;It&amp;#x27;s even customary to brand them, drawing logotypes, designing unique styles, and indulging in other forms of branding of a product. This isn&amp;#x27;t bad, it&amp;#x27;s just something that can slip past your passionate eyes, and something I rarely see the authors of those projects mentioning.&lt;/p&gt;&lt;p&gt;Many of such projects are still built by enthusiastic and endlessly creative people but despite that, they are all destined to become a product one way or another.&lt;/p&gt;&lt;div&gt;Enthusiasm is great but it makes for a poor dinner.&lt;/div&gt;&lt;p&gt;In addition to that, any enthusiasm needs to be kept alive. And so, in the light of preserving the embers of their inspiration, it&amp;#x27;s natural that authors seek ways to sustain their projects financially. There&amp;#x27;s only one catch.&lt;/p&gt;&lt;p&gt;If you look at the list above, not a single point has anything to do with the actual open source work you&amp;#x27;re doing. The financial stability of authors comes from external sources—often built by themselves in addition to the titanic effort they&amp;#x27;ve already put into their projects.&lt;/p&gt;&lt;h2 id=&quot;money-in-open-source&quot;&gt;Money in open source&lt;/h2&gt;&lt;p&gt;Anytime I mention &amp;quot;money&amp;quot; and &amp;quot;open source&amp;quot; in a single sentence everybody suddenly gets quiet in the room. For whichever abnormal reason, it&amp;#x27;s an unspoken taboo to associate what is legally and infinitely free with such a dirty thing as money. I, honestly, don&amp;#x27;t get this. How can we taboo something that is not there, to begin with?&lt;/p&gt;&lt;div&gt;Because there&amp;#x27;s no money in open source.&lt;/div&gt;&lt;p&gt;Certainly, no contributors get into projects with the sole purpose to get a financial gain out of them. Open source has never been about money either. But for you as an author, the lack of funds to sustain your ideas and pay for even a small portion of the time you&amp;#x27;re spending on them is—I&amp;#x27;m not going to lie—devastating. It may not be your concern at first but it will inevitably become one when your ideas gain popularity, demanding significantly more time than there are hours in a day.&lt;/p&gt;&lt;p&gt;That being said, there are unicorn projects that exist with the support of their users. Whether your project will become one of those is a separate discussion. I&amp;#x27;d say you have a higher chance of winning multiple lotteries in a row than getting stable financial support out of voluntary sponsorships. The entire open source culture is designed so that there would be no need nor urge to sponsor creators, and there hasn&amp;#x27;t been a single shift in this direction for decades. Like a still windmill on a windless day, the financial state of open source hangs in the air, unresolved, unsought, stagnating.&lt;/p&gt;&lt;p&gt;That is why about everybody builds a product within or around their open source ideas. I can&amp;#x27;t say I share this direction but it&amp;#x27;s certainly the only one that is available to everybody. I do think that product-izing ideas leads to a priority shift, which often ends up in decisions that are best for the product and not for its users. In the end, when it comes to product, it&amp;#x27;s the revenue that&amp;#x27;s driving it, and so everybody is stuck in the infinite tug-o-war of attracting the users to their product and their product alone.&lt;/p&gt;&lt;h2 id=&quot;plan-your-exit&quot;&gt;Plan your exit&lt;/h2&gt;&lt;p&gt;The sooner you begin thinking of your ideas like products, the healthier your open source (and personal) life will become. And with any product, it&amp;#x27;s worthwhile to consider your exiting options. In the context of open source, those are the options that will keep your product afloat, and your creativity fed.&lt;/p&gt;&lt;p&gt;It&amp;#x27;s not a coincidence that most of the projects I can think of have a clear maintenance strategy in place. I&amp;#x27;m not advocating for you to go and write a SaaS in parallel to your other ideas but if you can, then certainly go ahead. In other cases, consider the future of your project and think about the ways for that future not to become your second job. If I was able to carry at least some of my point across in the previous section, then you&amp;#x27;d be able to answer by yourself exactly how well that job will be paid.&lt;/p&gt;&lt;p&gt;To make things somewhat more challenging, not all ideas translate to paid products. You may come up with fantastic tools that won&amp;#x27;t sell both on their own or as a part of another product, leaving you in a state of limbo. Don&amp;#x27;t wait for that to happen, devise a sensible maintenance plan for your projects early on, and stick to it.&lt;/p&gt;&lt;p&gt;I would love to recommend specific things or give you a sense of direction to follow when it comes to sustainable open source. But I can&amp;#x27;t. Not because I want to keep the secrets of the universe from you but because I haven&amp;#x27;t quite figured this out myself (hi from the limbo! 👋). My projects stagnate, and I&amp;#x27;m burning out spending so much of my free time on them. Once I make sense of it, I will write another piece, but for now, the best I can do is to warn you and hope that you will be better prepared that I was.&lt;/p&gt;&lt;h2 id=&quot;establish-a-healthy-balance&quot;&gt;Establish a healthy balance&lt;/h2&gt;&lt;p&gt;Even from the earliest of days when you&amp;#x27;re building something for fun with no intention or hope that it will ever be discovered by anybody, keep a healthy balance in the time you spend on open source. When your project does get discovered, and people will start demanding more, it&amp;#x27;d be too late to think about things like this.&lt;/p&gt;&lt;p&gt;Open source is a vast sea of possibilities, and it&amp;#x27;s easy to drown in it, watching as hours and weeks fly by, and your ideas gradually take form. It&amp;#x27;s captivating and, frankly, addicting to do open source but be careful not to sacrifice your personal and family time to that addiction. This may sound silly to you but I know how easy it is to get a light panic attack whenever I see someone reporting an issue on my project. It also gets unnerving to realize that I cannot move as fast with my ideas as I want to simply because there are too many of them. Mental health is not something to joke about, and it&amp;#x27;s different things that make us happy, sad, nervous, or uplifted. That&amp;#x27;s where a proper balance and attitude towards open source is crucial.&lt;/p&gt;&lt;p&gt;Honestly, I would recommend just setting a time limit on the open source activities you do during the day. &amp;quot;Okay, it&amp;#x27;s 5 PM so I have one and a half hours to contribute to open source,&amp;quot; that&amp;#x27;s where a good balance starts. Sure, you may grow to afford more time, but it&amp;#x27;s the idea of clear activity boundaries that makes it healthy. Don&amp;#x27;t work in the evenings or nights. Don&amp;#x27;t forget about your friends and your loved ones. Trust me, they matter more than all the code in the world.&lt;/p&gt;&lt;p&gt;And once you get &amp;quot;big&amp;quot;, try keeping things simple. Don&amp;#x27;t let it get into your head. Don&amp;#x27;t stress out about silly things. And yes, the code we write, in the end, is just a silly thing. It doesn&amp;#x27;t make it uninteresting or pointless, but it&amp;#x27;s just that—bytes in the computer. Funny enough, switching your attitude to a more distant one will also have a positive effect on the work you do. Everybody wins when the creator is healthy.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;&lt;p&gt;There is no better place to learn engineering than open source. There is also no better place to sacrifice your personal life and mental health than open source. And the way you balance between these two purely depends on you and the culture you establish about &lt;em&gt;your&lt;/em&gt; open source work. Make your ideas happen. Help others. And, above all, remember to take a week off of open source once in a while, it will pay off.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why I wouldn't want to have an engineering degree]]></title><description><![CDATA[Coming from a software engineer, the title of this article is rather uncalled for. That's the thing—I am not an engineer. Well, at least…]]></description><link>https://redd.one/why-i-wouldnt-want-to-have-an-engineering-degree</link><guid isPermaLink="false">https://redd.one/why-i-wouldnt-want-to-have-an-engineering-degree</guid><pubDate>Mon, 23 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Coming from a software engineer, the title of this article is rather uncalled for. That&amp;#x27;s the thing—I am &lt;em&gt;not&lt;/em&gt; an engineer. Well, at least that&amp;#x27;s not what my diploma says. I&amp;#x27;ve been programming since childhood and was extremely lucky to see my bedroom hobby evolve into professional employment about half a decade ago. All that without reading a single programming book or completing an online course (which isn&amp;#x27;t bragging, it&amp;#x27;s rather sad, really). And, of course, without having a respective degree. Thinking about it now, I&amp;#x27;d rather not have an engineering education, and I&amp;#x27;m going to tell you why.&lt;/p&gt;&lt;p&gt;First of all, I have nothing against education in general and engineering degrees in particular. Official education, although variable in affordability and quality, is a great chance to expand one&amp;#x27;s knowledge in a subject of their interest and an unmatched way to connect with hundreds of like-minded folks. It used to be a requirement to land a job in IT but I&amp;#x27;m happy to see the trend of degreeless offers from the industry giants like Google and Microsoft.&lt;/p&gt;&lt;div&gt;The world is slowly coming to realize that a diploma is a receipt for education, but by no means a guarantee of time or effort invested.&lt;/div&gt;&lt;p&gt;People studying something they aren&amp;#x27;t excited about are the people I fear the most. I&amp;#x27;ve witnessed hundreds of students ignoring lectures, paying off exams, and looking for each and every opportunity not to study. Nothing unusual, right? Students are students and this kind of behavior is more than common and (sadly) accepted everywhere. Only the people I saw indulging in such behavior were medical students—surgeons, pediatricians, dentists—people you trust with your &lt;em&gt;life&lt;/em&gt;. Seeing this made me realize how important it is to love what you do, and how no amount of education can spark an interest where there is none.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Of course, there are exceptions to the rules. I did, and still do, have an interest in medicine, and that was the main reason I&amp;#x27;ve studied to be a doctor. I do believe people may enjoy multiple things in life, and me becoming a software engineer is living proof of that.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have plenty of interest in engineering, yet I still wouldn&amp;#x27;t want to study it in school. No, not because it may be expensive or may not bring enough practical value for its worth. I wouldn&amp;#x27;t want to have an engineering degree because it would rid me of the feeling of &lt;em&gt;discovery&lt;/em&gt;.&lt;/p&gt;&lt;div&gt;I won&amp;#x27;t deny that coming from a non-technical background is challenging.&lt;/div&gt;&lt;p&gt;Extremely challenging. From people looking down on you for lacking a colorful paper saying you&amp;#x27;re as smart as them, to the sheer lack of knowledge in various domains—there were numerous times when I thought that having a relevant education might&amp;#x27;ve helped. I&amp;#x27;m sure it would&amp;#x27;ve helped, but it&amp;#x27;d mask the problem, not solve it.&lt;/p&gt;&lt;p&gt;Okay, what about that &amp;quot;discovery&amp;quot; this random human from the internet mentioned? To explain to you what I mean by that I need to tell you about one particular lunch I had.&lt;/p&gt;&lt;p&gt;Back in the early days of my open-source &lt;del&gt;countless unpaid weekend hours to make the world a better place&lt;/del&gt; endeavors, I met one of my colleagues for a lunch. He is a rather unordinary person and one of the smartest engineers I know. I&amp;#x27;d often bring up some struggles I had with my projects both to &lt;a href=&quot;/blog/how-to-ask-questions&quot;&gt;learn to ask questions&lt;/a&gt; and to, hopefully, get some help. I remember that time well because I was having trouble with one of the libraries I&amp;#x27;d been writing.&lt;/p&gt;&lt;p&gt;&amp;quot;I struggle with orchestrating multiple dependent asynchronous events,&amp;quot; I said.&lt;/p&gt;&lt;p&gt;&amp;quot;Good. Tell me about it,&amp;quot; replied my colleague.&lt;/p&gt;&lt;p&gt;For the next fifteen minutes, I&amp;#x27;d tried my best to explain the context and the issue, and it appeared that I succeeded. Now to bring some more context to you, I&amp;#x27;m not a fan of one-way conversations, so whenever I ask for help, I try to mention at least a few things I&amp;#x27;ve tried, and suggest my hypothesis as to why they failed. And thus I went through each approach I took that hadn&amp;#x27;t been successful.&lt;/p&gt;&lt;p&gt;Suddenly, my colleague stopped me and said: &amp;quot;You&amp;#x27;ve just described CQRS.&amp;quot;&lt;/p&gt;&lt;p&gt;&amp;quot;CQ-what-what, excuse me?&amp;quot; I asked, blinking like a donkey.&lt;/p&gt;&lt;p&gt;&amp;quot;Command Query Responsibility Segregation—CQRS. That&amp;#x27;s what you&amp;#x27;ve just said,&amp;quot; he reassured me. &amp;quot;That&amp;#x27;s something you can try to tackle your problem.&amp;quot;&lt;/p&gt;&lt;p&gt;He described CQRS to me, projecting the concept on my very use case, and then it struck me. I was describing that very pattern, although in my limited and extremely abstract vocabulary, all along. Something people may have studied or read in books came to me as a logical approach to the problem. Sure, had I known this, I wouldn&amp;#x27;t have had the issue to begin with, but then I&amp;#x27;d have missed an incredible discovery moment, which I still remember long years after.&lt;/p&gt;&lt;div&gt;Coming to a realization of certain concepts within your natural learning curve is the most wonderful thing about learning that no book or lecture can teach.&lt;/div&gt;&lt;p&gt;I&amp;#x27;ve tried implementing that new shiny abbreviation and, needless to say, I&amp;#x27;ve enjoyed the entire routine, mainly because I felt like I&amp;#x27;ve come to the solution myself. It&amp;#x27;s the personal achievements like this that stick in your mind the best. The result matters, I can&amp;#x27;t argue with that, but it&amp;#x27;s the &lt;em&gt;path&lt;/em&gt; that led us to that result that advances our understanding.&lt;/p&gt;&lt;p&gt;You may be thinking I&amp;#x27;ve just got a certain inclination or a habit of mind, and that&amp;#x27;s why some concepts click for me. That cannot be farther from the truth. I&amp;#x27;m a linguistic sponge to the core of my bones and catch on grammatic intricacies tenfold faster than on the most basic mathematic laws and rules. I don&amp;#x27;t even see software engineering as mathematics, objectively lacking in profound understanding, but keeping my thinking process clear, almost child-like, nonetheless.&lt;/p&gt;&lt;p&gt;We, humans, are incredibly proficient at justifying our lack of will. There is no such lie in the world that we wouldn&amp;#x27;t tell ourselves just not to admit that we&amp;#x27;ve given up. Nobody likes to admit their flaws, I get it. Yet how would you improve without acknowledging you need an improvement first?&lt;/p&gt;&lt;p&gt;In conclusion, I want to encourage you to learn, and, before all, learn how to learn. Let the lack of a degree not stop you. Let the complexity not scare you. Come to conclusions, make mistakes, have fun. You&amp;#x27;ve got this.&lt;/p&gt;&lt;div&gt;And remember: when you hit a wall, don&amp;#x27;t focus on the shortest most efficient way around it. Focus on trying to understand the nature of the wall and why it&amp;#x27;s there.&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Writing a custom webpack loader]]></title><description><![CDATA[Introduction This article uses the latest webpack version, which at the moment of writing is webpack v5 . webpack is a backbone that…]]></description><link>https://redd.one/writing-custom-webpack-loader</link><guid isPermaLink="false">https://redd.one/writing-custom-webpack-loader</guid><pubDate>Sun, 06 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;This article uses the latest webpack version, which at the moment of writing is &lt;strong&gt;webpack v5&lt;/strong&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://webpack.js.org/&quot;&gt;webpack&lt;/a&gt; is a backbone that powers most of the modern frontend tooling: Create React App, NextJS, Gatsby, —the list can go on and on. Over the years webpack had grown a reputation of being hard to configure, which is rather unfortunate, because no matter how abstracted webpack is in your setup, you can always benefit from knowing how to customize it.&lt;/p&gt;&lt;p&gt;Today I&amp;#x27;d like to speak about one specific way of customizing webpack: writing a custom loader. I&amp;#x27;m surprised to find this topic rather undocumented, so most of the information you read in this article is a result of reverse-engineering multiple existing loaders to let you and me learn.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;basics&quot;&gt;Basics&lt;/h2&gt;&lt;p&gt;A &lt;a href=&quot;https://webpack.js.org/concepts/loaders/&quot;&gt;&lt;em&gt;loader&lt;/em&gt;&lt;/a&gt; in webpack is a function that transforms a source code of imported modules.&lt;/p&gt;&lt;p&gt;Think of a loader as a function of a module&amp;#x27;s content that creates or augments that module&amp;#x27;s export:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;const styles = cssLoader(require(&amp;#x27;./styles.css&amp;#x27;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Loaders are the heart and soul of how webpack compiles TypeScript to JavaScript, turns SASS into CSS, and JSX into &lt;code&gt;React.createElement&lt;/code&gt; calls. In fact, webpack doesn&amp;#x27;t really do all that, loaders do! Meanwhile, webpack is responsible for establishing a transformation chain for your source code and making sure that the loaders are executed at the right stage of the build process.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s how you apply loaders to files in a webpack configuration:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// webpack.config.js
module.exports = {
module: {
rules: [
{
// Capture all &amp;quot;*.js&amp;quot; imports,
test: /\.js$/,
// ...and transform them via &amp;quot;babel-loader&amp;quot;.
use: [&amp;#x27;babel-loader&amp;#x27;],
},
{
// Capture all the &amp;quot;*.css&amp;quot; imports,
test: /\.css$/
// ...and transform them via &amp;quot;css-loader&amp;quot;.
use: [&amp;#x27;css-loader&amp;#x27;]
}
],
},
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the configuration above, all &lt;code&gt;*.js&lt;/code&gt; files that you import are passed through the &lt;code&gt;babel-loader&lt;/code&gt;, while all &lt;code&gt;*.css&lt;/code&gt; files are transformed via the &lt;code&gt;css-loader&lt;/code&gt;. You can provide a list of loaders for the same subset of modules, in which case the loaders are applied from &lt;strong&gt;right to left&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{
test: /\.ext$/
use: [&amp;#x27;third-loader&amp;#x27;, &amp;#x27;second-loader&amp;#x27;, &amp;#x27;first-loader&amp;#x27;]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This makes more sense once you look at a loader as a function that passes its result (transformed code) to the next loader in the chain:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;third(second(first(source)))
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt;&lt;p&gt;Loaders are designed to transform code. Unlike plugins, loaders cannot affect the build process, but rather transform individual imported modules during the build.&lt;/p&gt;&lt;p&gt;Generally, anything that lies outside of processing and generating code can be achieved through &lt;a href=&quot;https://webpack.js.org/concepts/#plugins&quot;&gt;plugins&lt;/a&gt;. At the same time, one thing that a plugin shouldn&amp;#x27;t do is transform code, making the boundary between a loader and a plugin rather clear.&lt;/p&gt;&lt;p&gt;For example, here are some of the use-cases to write a custom loader:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Support imports of a custom file format (i.e. &lt;code&gt;*.graphql&lt;/code&gt; or &lt;code&gt;*.prisma&lt;/code&gt;);&lt;/li&gt;&lt;li&gt;Append meta data to the transformed files (i.e. inject frontmatter to &lt;code&gt;*.mdx&lt;/code&gt; files).&lt;/li&gt;&lt;li&gt;Transform imported modules (i.e. auto-prefix imported &lt;code&gt;*.css&lt;/code&gt; files).&lt;/li&gt;&lt;/ul&gt;&lt;hr/&gt;&lt;h2 id=&quot;writing-a-custom-loader&quot;&gt;Writing a custom loader&lt;/h2&gt;&lt;p&gt;In this article we&amp;#x27;re going to write an MP3 loader that would turn any &lt;code&gt;*.mp3&lt;/code&gt; import into a &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; component for playing that audio file.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import AudioPlayer from &amp;#x27;./audio.mp3&amp;#x27;
function MyComponent {
return (
&amp;lt;AudioPlayer /&amp;gt;
)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;declaration&quot;&gt;Declaration&lt;/h3&gt;&lt;p&gt;A loader is a function that accepts a source code and returns a transformed source code. Let&amp;#x27;s start from that: create a &lt;code&gt;mp3-loader.js&lt;/code&gt; file and declare a new function in there:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/mp3-loader.js
module.exports = function (source) {
return source
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At the current state, your loader is going to return the imported source (MP3 file binary) as-is.&lt;/p&gt;&lt;p&gt;We will be using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio&quot;&gt;&lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt;&lt;/a&gt; HTML element to play an imported audio file. For that, we need to know the path of the imported MP3 file, emit it in the webpack&amp;#x27;s build directory, and provide that file path as the &lt;code&gt;src&lt;/code&gt; attrbiute of the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; element.&lt;/p&gt;&lt;p&gt;We can get an absolute path of the imported module by accessing the &lt;code&gt;this.resourcePath&lt;/code&gt; property in a loader&amp;#x27;s context. For example, given the following import:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;import AudioPlayer from &amp;#x27;./audio.mp3&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;this.resourcePath&lt;/code&gt; will contain the absolute path to the &lt;code&gt;./audio.mp3&lt;/code&gt; file. Knowing this, let&amp;#x27;s emit an mp3 file with the same name:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/mp3-loader.js
const path = require(&amp;#x27;path&amp;#x27;)
module.exports = function (source) {
// webpack exposes an absolute path to the imported module
// under the &amp;quot;this.resourcePath&amp;quot; property. Get the file name
// of the imported module. For example:
// &amp;quot;/User/admin/audio.mp3&amp;quot; (this.resourcePath) -&amp;gt; &amp;quot;audio.mp3&amp;quot;.
const filename = path.basename(this.resourcePath)
// Next, create an asset info object.
// webpack uses this object when outputting the build&amp;#x27;s stats,
// so you could see info about the emitted asset.
const assetInfo = { sourceFilename: filename }
// Finally, emit the imported audio file&amp;#x27;s &amp;quot;source&amp;quot;
// in the webpack&amp;#x27;s build directory using a built-in
// &amp;quot;emitFile&amp;quot; method.
this.emitFile(filename, source, null, assetInfo)
// For now, return the mp3 binary as-is.
return source
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Note that you need to remain in the webpack&amp;#x27;s context in order to access &lt;code&gt;this.resourcePath&lt;/code&gt; and &lt;code&gt;this.emitFile&lt;/code&gt;. Ensure your loader is &lt;em&gt;not&lt;/em&gt; an arrow function, because that&amp;#x27;s going to re-assign the loader&amp;#x27;s context and you&amp;#x27;ll lose access to the properties and methods that webpack exposes to your loader.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Now the imported audio file will be emitted alongside our JavaScript modules. Let&amp;#x27;s proceed to the next step—returning a React component in our loader.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/mp3-loader.js
const path = require(&amp;#x27;path&amp;#x27;)
module.exports = function (source) {
const filename = path.basename(this.resourcePath)
const assetInfo = { sourceFilename: filename }
this.emitFile(filename, source, null, assetInfo)
return `
import React from &amp;#x27;react&amp;#x27;
export default function Player(props) {
return &amp;lt;audio controls src=&amp;quot;${filename}&amp;quot; /&amp;gt;
}
`
}
// Mark the loader as raw so that the emitted audio binary
// does not get processed in any way.
module.exports.raw = true
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Keep the imports that your loader needs in the loader&amp;#x27;s scope, while the imports that your &lt;em&gt;transformed code&lt;/em&gt; needs in its scope, inlined. Everything that a loader imports and does is an implementation detail of the build, and won&amp;#x27;t be accessible in the compiled bundle.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Both input and output of a loader&amp;#x27;s function must be a &lt;em&gt;string&lt;/em&gt;. That&amp;#x27;s why we declare a new &lt;code&gt;Player&lt;/code&gt; React component in a string, including all the necessary imports inside it. Now, whenever our application imports an MP3 file, instead of importing its binary, it&amp;#x27;ll import the &lt;code&gt;Player&lt;/code&gt; component that the loader generated.&lt;/p&gt;&lt;p&gt;Congratulations! You&amp;#x27;ve just written a custom webpack loader that turns MP3 files into interactive audio players. Now let&amp;#x27;s configure your webpack configuration to apply that loader to all the &lt;code&gt;*.mp3&lt;/code&gt; files.&lt;/p&gt;&lt;h2 id=&quot;using-a-custom-loader&quot;&gt;Using a custom loader&lt;/h2&gt;&lt;p&gt;There are two ways to use a loader: tell webpack to resolve it from a local file, or publish it and install as a regular dependency. Unless your loader&amp;#x27;s purpose is generic, or you plan to reuse it across multiple projects, I strongly recommend using the loader from a local file.&lt;/p&gt;&lt;h3 id=&quot;from-a-local-file&quot;&gt;From a local file&lt;/h3&gt;&lt;p&gt;To use a local webpack loader you need to alias it in the &lt;code&gt;resolveLoader&lt;/code&gt; property in your webpack configuration:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// webpack.config.js
const path = require(&amp;#x27;path&amp;#x27;)
module.exports = {
module: {
rules: [
{
test: /\.mp3$/,
// Reference the loader by the same name
// that you aliased in &amp;quot;resolveLoader.alias&amp;quot; below.
use: [&amp;#x27;babel-loader&amp;#x27;, &amp;#x27;mp3-loader&amp;#x27;],
},
],
},
resolveLoader: {
alias: {
&amp;#x27;mp3-loader&amp;#x27;: path.resolve(__dirname, &amp;#x27;src/mp3-loader.js&amp;#x27;),
},
},
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Since we&amp;#x27;ve returne JSX from our loader, we need to tell webpack to transform it into regular JavaScript. That&amp;#x27;s why we&amp;#x27;ve included &lt;code&gt;babel-loader&lt;/code&gt; as the next loader to apply after our &lt;code&gt;mp3-loader&lt;/code&gt; is done (remember loaders are applied from right to left).&lt;/p&gt;&lt;/blockquote&gt;&lt;h3 id=&quot;from-a-package&quot;&gt;From a package&lt;/h3&gt;&lt;p&gt;If you decide to distribute your loader as a node module, you can use it just as any other Node.js dependency.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;It&amp;#x27;s a common convention to distribute loaders in the &lt;code&gt;[name]-loader&lt;/code&gt; format. Consider this when publishing your loader.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Let&amp;#x27;s say you&amp;#x27;ve published your loader under a &lt;code&gt;mp3-loader&lt;/code&gt; package on NPM. This is how you would use it in your project:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;npm install mp3-loader
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.mp3$/,
use: [&amp;#x27;babel-loader&amp;#x27;, &amp;#x27;mp3-loader&amp;#x27;],
},
],
},
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Notice that you don&amp;#x27;t have to import your loader, webpack will resolve it from &lt;code&gt;node_modules&lt;/code&gt; automatically.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;trying-your-loader&quot;&gt;Trying your loader&lt;/h2&gt;&lt;p&gt;To see the &lt;code&gt;mp3-loader&lt;/code&gt; in action, run the &lt;code&gt;webpack&lt;/code&gt; CLI command, given you&amp;#x27;ve configured your &lt;code&gt;webpack.config.js&lt;/code&gt; to use the custom loader:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;$ npx webpack
asset audio.mp3 2.38 MiB [compared for emit] [from: src/audio.mp3] (auxiliary name: main)
asset main.js 858 KiB [compared for emit] (name: main)
webpack 5.37.0 compiled successfully in 1347 ms
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can inspect the end result of the &lt;code&gt;mp3-loader&lt;/code&gt; in this example repository:&lt;/p&gt;&lt;div owner=&quot;redd-developer&quot; repo=&quot;webpack-custom-loader&quot;&gt;&lt;/div&gt;&lt;h2 id=&quot;testing-your-loader&quot;&gt;Testing your loader&lt;/h2&gt;&lt;p&gt;Since loaders depend on the compilation context, I recommend testing them as a part of webpack&amp;#x27;s compilation, making such tests integration tests. Your tests&amp;#x27; expectations will depend on what your loader does, so make sure to model them accordingly.&lt;/p&gt;&lt;p&gt;In the case of our &lt;code&gt;mp3-loader&lt;/code&gt; we expect two things to happen:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Imported audio file must be emitted in the build assets;&lt;/li&gt;&lt;li&gt;Compiled code must create an &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; React component.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s reflect those expectations in a test:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// test/mp3-loader.test.js
const path = require(&amp;#x27;path&amp;#x27;)
const webpack = require(&amp;#x27;webpack&amp;#x27;)
const { createFsFromVolume, Volume } = require(&amp;#x27;memfs&amp;#x27;)
// A custom wrapper to promisify webpack compilation.
function compileAsync(compiler) {
return new Promise((resolve, reject) =&amp;gt; {
compiler.run((error, stats) =&amp;gt; {
if (error || stats.hasErrors()) {
const resolvedError = error || stats.toJson(&amp;#x27;errors-only&amp;#x27;)[0]
reject(resolvedError.message)
}
resolve(stats)
})
})
}
it(&amp;#x27;converts &amp;quot;*.mp3&amp;quot; import into an audio player&amp;#x27;, async () =&amp;gt; {
// Configure a webpack compiler.
const compiler = webpack({
mode: &amp;#x27;development&amp;#x27;,
entry: path.resolve(__dirname, &amp;#x27;../src/index.js&amp;#x27;),
output: {
filename: &amp;#x27;index.js&amp;#x27;,
},
module: {
rules: [
{
test: /\.mp3$/,
use: [&amp;#x27;babel-loader&amp;#x27;, require.resolve(&amp;#x27;../src/mp3-loader.js&amp;#x27;)],
},
{
test: /\.js$/,
use: [&amp;#x27;babel-loader&amp;#x27;],
},
],
},
})
// Create an in-memory file system so that the build assets
// are not emitted to disk during test runs.
const memoryFs = createFsFromVolume(new Volume())
compiler.outputFileSystem = memoryFs
// Compile the bundle.
await compileAsync(compiler)
// Expect the imported audio file to be emitted alongside the build.
expect(compiler.outputFileSystem.existsSync(&amp;#x27;dist/audio.mp3&amp;#x27;)).toEqual(true)
// Expect the compiled code to create an &amp;quot;audio&amp;quot; element in React.
const compiledCode = compiler.outputFileSystem.readFileSync(
&amp;#x27;dist/index.js&amp;#x27;,
&amp;#x27;utf8&amp;#x27;
)
expect(compiledCode).toContain(&amp;#x27;.createElement(\\&amp;quot;audio\\&amp;quot;&amp;#x27;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;h2 id=&quot;recipes&quot;&gt;Recipes&lt;/h2&gt;&lt;h3 id=&quot;loader-options&quot;&gt;Loader options&lt;/h3&gt;&lt;p&gt;Loaders can accept options to change their behavior.&lt;/p&gt;&lt;p&gt;You can pass options to a loader in the webpack configuration:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.mp3$/,
use: [
{
loader: &amp;#x27;mp3-loader&amp;#x27;,
options: {
maxSizeBytes: 1000000,
},
},
],
},
],
},
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Above, we&amp;#x27;ve created a custom &lt;code&gt;maxSizeBytes&lt;/code&gt; option for the loader. The &lt;code&gt;options&lt;/code&gt; object can later be accessed in the loader by calling &lt;code&gt;this.getOptions()&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/mp3-loader.js
module.exports = function (source) {
const options = this.getOptions()
console.log(options.maxSizeBytes)
// ...parametrize your loader&amp;#x27;s behavior.
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;validating-options&quot;&gt;Validating options&lt;/h4&gt;&lt;p&gt;It&amp;#x27;s a good tone to validate your loader&amp;#x27;s options to prevent improper usage and narrow down issues.&lt;/p&gt;&lt;p&gt;webpack comes with a &lt;code&gt;schema-utils&lt;/code&gt; dependency installed, which you can use to validate your loader&amp;#x27;s options:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=4-11,16-17&quot; focusedLines=&quot;4-11,16-17&quot;&gt;// src/mp3-loader.js
const { validate } = require(&amp;#x27;schema-utils&amp;#x27;)
// Describe your loader&amp;#x27;s options in a JSON Schema.
const schema = {
properties: {
maxSizeBytes: {
type: &amp;#x27;number&amp;#x27;,
},
},
}
module.exports = function (source) {
const options = this.getOptions()
// Validate the options early in your loader.
validate(schema, options)
// ...the rest of your loader.
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;The &lt;code&gt;schema&lt;/code&gt; object is defined using the &lt;a href=&quot;https://json-schema.org/&quot;&gt;JSON Schema&lt;/a&gt; format.&lt;/p&gt;&lt;/blockquote&gt;&lt;h3 id=&quot;logger&quot;&gt;Logger&lt;/h3&gt;&lt;p&gt;Here&amp;#x27;s an example of how we can take the custom &lt;code&gt;maxSizeBytes&lt;/code&gt; &lt;a href=&quot;#loader-options&quot;&gt;loader option&lt;/a&gt; into account, and produce a warning if the imported audio file exceeds the maximum allowed size:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=6,10&quot; focusedLines=&quot;6,10&quot;&gt;// src/mp3-loader.js
const fs = require(&amp;#x27;fs&amp;#x27;)
module.exports = function (source) {
const options = this.getOptions()
const logger = this.getLogger()
const assetStats = fs.statSync(this.resourcePath)
if (assetStats.size &amp;gt; options.maxSizeBytes) {
logger.warn(&amp;#x27;Imported MP3 file is too large!&amp;#x27;)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Learn more about the webpack&amp;#x27;s &lt;a href=&quot;https://webpack.js.org/api/logging/&quot;&gt;logger interface&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;h3 id=&quot;context-properties&quot;&gt;Context properties&lt;/h3&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Property&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;this.resourcePath&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Absolute path to the imported module.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;this.rootContext&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Compilation &lt;a href=&quot;https://webpack.js.org/configuration/entry-context/#context&quot;&gt;context&lt;/a&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;blockquote&gt;&lt;p&gt;See all the available properties by inspecting &lt;code&gt;this&lt;/code&gt; in your loader.&lt;/p&gt;&lt;/blockquote&gt;&lt;h3 id=&quot;context-methods&quot;&gt;Context methods&lt;/h3&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;this.emitFile()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Emits a file to the build directory.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;this.getLogger()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Returns an internal webpack &lt;a href=&quot;https://webpack.js.org/api/logging/&quot;&gt;logger instance&lt;/a&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;this.emitWarning()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Emits a warning during the compilation.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;this.getOptions()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Returns the loader&amp;#x27;s options.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;blockquote&gt;&lt;p&gt;See all the available methods by inspecting &lt;code&gt;this&lt;/code&gt; in your loader.&lt;/p&gt;&lt;/blockquote&gt;&lt;hr/&gt;&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;&lt;p&gt;The topic of webpack customization is rather undocumented and difficult to scout for. When learning about writing webpack loaders, I&amp;#x27;ve referred to multiple existing loaders that I&amp;#x27;ve used in the past to see how they work. Below you can find the list of such loaders to reference yourself:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://webpack.js.org/loaders/&quot;&gt;&lt;strong&gt;Featured webpack loaders&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/webpack-contrib/file-loader/&quot;&gt;file-loader&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/webpack-contrib/css-loader&quot;&gt;css-loader&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Thinking in Functions, Part II: Higher-order functions]]></title><description><![CDATA[In the previous article, we have learned about the Input/Output pattern and how to use it when writing functions Today I'd like to…]]></description><link>https://redd.one/thinking-in-functions-higher-order-functions</link><guid isPermaLink="false">https://redd.one/thinking-in-functions-higher-order-functions</guid><pubDate>Sun, 07 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the previous article, we have learned about the &lt;a href=&quot;/blog/thinking-in-functions&quot;&gt;Input/Output pattern&lt;/a&gt; and how to use it when writing functions Today I&amp;#x27;d like to continue the series by talking about one of the most powerful concepts of functional programming—higher-order functions.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;higher-order-function&quot;&gt;Higher-order function&lt;/h2&gt;&lt;p&gt;A &lt;em&gt;higher-order function&lt;/em&gt; is a function that accepts another function as an argument or returns a function. Or both.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s an example of a higher-order function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Function `fn` accepts `anotherFn` as an argument,
function fn(anotherFn) {
// ...and calls it with `x` to get the value of `y`.
const y = anotherFn(x)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While this example is rather abstract and may look unfamiliar, there are multiple higher-order functions that you&amp;#x27;re already using on a daily basis. In JavaScript, a lot of standard data types methods are higher-order functions. For example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map&quot;&gt;&lt;code&gt;Array.prototype.map&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/replace&quot;&gt;&lt;code&gt;String.prototype.replace&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce&quot;&gt;&lt;code&gt;Array.prototype.reduce&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;All those functions accept another function as an argument and that makes them higher-order functions. Let&amp;#x27;s analyze what that means by taking a closer look at &lt;code&gt;Array.prototype.map&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const numbers = [1, 2, 3]
// Go through each number in the `numbers` array
// and multiply it by 2.
numbers.map((number) =&amp;gt; number * 2) // [2, 4, 6]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You know that the &lt;code&gt;.map()&lt;/code&gt; method goes (iterates) through each array member and applies some transformation to it. Notice how you never see the &amp;quot;goes through each array member&amp;quot; logic while using this method, you only describe the transformation to apply. That&amp;#x27;s because &lt;code&gt;.map()&lt;/code&gt; is responsible for iteration, and when it comes to the point to map each value, it executes the function accepted as an argument.&lt;/p&gt;&lt;p&gt;This is the key principle of higher-order functions: &lt;em&gt;logic encapsulation&lt;/em&gt;.&lt;/p&gt;&lt;div&gt;Higher-order functions encapsulate certain behavior, delegating the other behavior to the function(s) they accept as an argument.&lt;/div&gt;&lt;p&gt;By doing so, the &lt;code&gt;.map()&lt;/code&gt; function establishes a usage contract with us. As with any contract, there are some terms to make it work:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A higher-order function controls when to call a passed function;&lt;/li&gt;&lt;li&gt;A higher-order function controls what arguments are to the accepted function.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Both those requirements are related to the fact that higher-order functions accept a &lt;em&gt;function definition&lt;/em&gt;, in other words: instruction for the action. The given function definition is accessed by the higher-order function as an argument, making it in charge of when and how to call a given function.&lt;/p&gt;&lt;p&gt;To better understand this concept, let&amp;#x27;s build our own &lt;code&gt;map&lt;/code&gt; function.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=8-10&quot; focusedLines=&quot;8-10&quot;&gt;function map(arr, mapFn) {
let result = []
for (let i = 0; i &amp;lt; arr.length; i++) {
// Get the current array member by index.
const member = arr[i]
// Call the `mapFn` function we accept as an argument,
// and provide the current array member to it.
const mappedValue = mapFn(member)
// Push the result of the `mapFn` function
// into the end array of transformed members.
result.push(mappedValue)
}
return result
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our custom &lt;code&gt;map&lt;/code&gt; function can be used like so:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;map([1, 2, 3], (number) =&amp;gt; number * 2)
// Identical to:
// [1, 2, 3].map((number) =&amp;gt; number * 2)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see that the iteration details like the &lt;code&gt;for&lt;/code&gt; cycle and the internal &lt;code&gt;results&lt;/code&gt; array are not exposed to the &lt;code&gt;mapFn&lt;/code&gt; function, and our custom &lt;code&gt;map&lt;/code&gt; function controls precisely when to call the given &lt;code&gt;mapFn&lt;/code&gt; argument and what data to provide it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;lineNumberStart=10&quot; lineNumberStart=&quot;10&quot;&gt;const mappedValue = mapFn(member)
&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;Higher-order functions control when and how to call an argument function.&lt;/div&gt;&lt;p&gt;The point of our &lt;code&gt;map&lt;/code&gt; function is that it can do much more than the multiplication of numbers. In fact, as it accepts a function that controls what to do with each array member, I dare say our &lt;code&gt;map&lt;/code&gt; function can do anything!&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;map([&amp;#x27;buy&amp;#x27;, &amp;#x27;gold&amp;#x27;], (word) =&amp;gt; word.toUpperCase())
// [&amp;quot;BUY&amp;quot;, &amp;quot;GOLD&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But why this function is so powerful? Because it encapsulates the &lt;em&gt;how&lt;/em&gt; (iteration) and accepts the &lt;em&gt;what&lt;/em&gt; (transformation). It keeps the logic that&amp;#x27;s a part of its contract hidden, but provides a way to customize a certain behavior via arguments to remain versatile.&lt;/p&gt;&lt;h3 id=&quot;returning-a-function&quot;&gt;Returning a function&lt;/h3&gt;&lt;p&gt;A higher-order function may also return a function. In that case, it acts opposite: instead of being in charge of when and how to call a given function, it generates a function and makes &lt;em&gt;you&lt;/em&gt; in charge of when and how that function is called.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s use the same &lt;code&gt;map&lt;/code&gt; function we&amp;#x27;ve written earlier, but now rewrite it so it returns a function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=2,4&quot; focusedLines=&quot;2,4&quot;&gt;// Instead of accepting the array straight away,
function map(mapFn) {
// ...we return a function that accepts that array.
return (arr) =&amp;gt; {
let result = []
// ...leaving the iteration logic as-is.
for (let i = 0; i &amp;lt; arr.length; i++) {
const member = arr[i]
const mappedValue = mapFn(member)
result.push(mappedValue)
}
return result
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since the &lt;code&gt;map&lt;/code&gt; now accepts only one argument, we need to change the way we call it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;map((number) =&amp;gt; number * 2)([1, 2, 3])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;fn(x)(y)&lt;/code&gt; call signature is not common in JavaScript. Moreover, it&amp;#x27;s rather confusing.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;History digression:&lt;/strong&gt; Not a long time ago such, call signature was used in &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; to describe higher-order components, so it may ring some distant hook-less bells.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;showLineNumbers={false}&quot; showLineNumbers=&quot;{false}&quot;&gt;export default connect(options)(MyComponent)
&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Don&amp;#x27;t worry, we don&amp;#x27;t have to abide by this unusual call signature. Instead, let&amp;#x27;s break that &lt;code&gt;map&lt;/code&gt; function call into two separate functions.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Calling `map` now returns a new function.
const multiplyByTwo = map((number) =&amp;gt; number * 2)
// That returned function already knows it should
// multiply each array item by 2.
// Now we only call it with the actual array.
multiplyByTwo([1, 2, 3]) // [2, 4, 6]
// We can reuse the `multiplyByTwo` function
// without having to repeat what it does,
// only changing the data it gets.
multiplyByTwo([4, 5, 6]) // [8, 10, 12]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our &lt;code&gt;map&lt;/code&gt; function doesn&amp;#x27;t do any iteration on its own anymore, yet it &lt;em&gt;generates&lt;/em&gt; another function (&lt;code&gt;multiplyByTwo&lt;/code&gt;) that &lt;em&gt;remembers&lt;/em&gt; what transformations to do, only waiting for us to provide it with data.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;applications&quot;&gt;Applications&lt;/h2&gt;&lt;p&gt;A higher-order function is a great instrument to use when designing functions. The point of higher-order functions is to encapsulate logic. That encapsulation may solve one or more purposes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Abstract implementation details in favor of declarative code.&lt;/li&gt;&lt;li&gt;Encapsulate logic for versatile reuse.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;abstract-logic&quot;&gt;Abstract logic&lt;/h3&gt;&lt;p&gt;The most basic example is when you need to repeat a certain action N amount of times. Functional abstractions can come in handy compared to a lengthy &lt;code&gt;for&lt;/code&gt; loop. Of course, it would still be using the loop internally, abstracting the iteration as it matters not when you use the function.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function repeat(fn, times) {
for (let i = 0; i &amp;lt; times; i++) {
fn()
}
}
// Where are not concerned with &amp;quot;how&amp;quot; (iteration),
// but focus on the &amp;quot;what&amp;quot; (declaration), making
// this function declarative.
repeat(() =&amp;gt; console.log(&amp;#x27;Hello&amp;#x27;), 3)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By moving the imperative code to the implementation details of a higher-order function, we often gain improved code readability, making our logic easier to reason about. Compare these two examples: one with imperative code and another with declarative higher-order function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Imperative
const letters = [&amp;#x27;a&amp;#x27;, &amp;#x27;b&amp;#x27;, &amp;#x27;c&amp;#x27;]
const nextLetters = []
for (let i = 0; i &amp;lt; letters.length; i++) {
nextLetters.push(letters[i].toUpperCase())
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Declarative
const letters = [&amp;#x27;a&amp;#x27;, &amp;#x27;b&amp;#x27;, &amp;#x27;c&amp;#x27;]
const nextLetters = map(letters, (letter) =&amp;gt; letter.toUpperCase())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While both examples map Array letters to upper case, you need much less cognitive effort to understand that intention behind the second example.&lt;/p&gt;&lt;div&gt;It&amp;#x27;s not about the amount of code, but about the code that describes implementation vs. the code that describes an intention.&lt;/div&gt;&lt;p&gt;It&amp;#x27;s also about reusing and composing—creating new logic by combining existing functions. Take a look at how the abstractions below give you an idea of what&amp;#x27;s happening without you peaking into how they are written:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;map(ids, toUserDetail)
map(users, toPosts)
reduce(posts, toTotalLikes)
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;encapsulate-logic&quot;&gt;Encapsulate logic&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s say we have a &lt;code&gt;sort&lt;/code&gt; function in our application:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function sort(comparator, array) {
array.sort(comparator)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are using this function to sort multiple things by rating: products, books, users.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;sort((left, right) =&amp;gt; left.rating - right.rating, products)
sort((left, right) =&amp;gt; left.rating - right.rating, books)
sort((left, right) =&amp;gt; left.rating - right.rating, users)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see how the comparator function is the same for each call, regardless of what data we&amp;#x27;re working with. We sort by rating &lt;em&gt;a lot&lt;/em&gt; in our app, so let&amp;#x27;s abstract that comparator into its own function and reuse it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=1-3&quot; focusedLines=&quot;1-3&quot;&gt;function byRating(left, right) {
return left.rating - right.rating
}
sort(byRating, products)
sort(byRating, books)
sort(byRating, users)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;#x27;s better! Our sorting calls, however, still operate on the criteria-agnostic &lt;code&gt;sort&lt;/code&gt; function. It&amp;#x27;s a minor thing, but we also have to import two functions (&lt;code&gt;sort&lt;/code&gt; and &lt;code&gt;byRating&lt;/code&gt;) anywhere we need to sort by rating.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s take the comparator out of the equation and lock it in a &lt;code&gt;sortByRating&lt;/code&gt; function that sorts a given array by rating straight away:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function sortByRating(array) {
sort(byRating, array)
}
sortByRating(products)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now the rating comparator is built-in into the &lt;code&gt;sortByRating&lt;/code&gt; function and we can reuse it anywhere we sort by rating. It&amp;#x27;s a single function, it&amp;#x27;s short, it&amp;#x27;s great. Case closed.&lt;/p&gt;&lt;p&gt;Our application grows in size and requirements, and we find ourselves sorting not only by rating, but also by reviews and downloads. If we follow the same abstraction strategy further, we&amp;#x27;ll stumble upon a problem:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=2,6,10&quot; focusedLines=&quot;2,6,10&quot;&gt;function sortByRating(array) {
sort(byRating, array)
}
function sortByReviews(array) {
sort(byReviews, array)
}
function sortByDownloads(array) {
sort(byDownloads, array)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because we&amp;#x27;ve moved out the comparator from the &lt;code&gt;sortBy*&lt;/code&gt; arguments, whenever we need to encapsulate a different comparison logic, we inevitably create a new function. By doing so, we&amp;#x27;re introducing a different kind of problem: neither of the &lt;code&gt;sortBy*&lt;/code&gt; functions above &lt;em&gt;share&lt;/em&gt; the intention of sorting an array, instead they repeat the implementation (&lt;code&gt;sort&lt;/code&gt;) all over the place.&lt;/p&gt;&lt;p&gt;We can approach this abstraction task with higher-order functions, which would allow us to create exactly &lt;em&gt;one&lt;/em&gt; concise and deterministic function to satisfy our requirements.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function sort(comparator) {
return (array) =&amp;gt; {
array.sort(comparator)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;sort&lt;/code&gt; function accepts a &lt;code&gt;comparator&lt;/code&gt; and returns an applicator function that does the sorting. Notice how the nature of &lt;code&gt;comparator&lt;/code&gt; and &lt;code&gt;array&lt;/code&gt; are variative, coming from arguments, yet the function&amp;#x27;s intention (&lt;code&gt;array.sort&lt;/code&gt;) does not repeat despite that dynamic nature.&lt;/p&gt;&lt;p&gt;Now we can create multiple sorting functions encapsulating different criteria like so:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const sortByRating = sort(byRating)
const sortByReviews = sort(byReviews)
const sortByDownloads = sort(byDownloads)
sortByRating(products)
sortByReviews(books)
sortByDownloads(songs)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a great example of logic encapsulation and reuse. It&amp;#x27;s &lt;em&gt;beautiful&lt;/em&gt; also.&lt;/p&gt;&lt;h3 id=&quot;mention-currying&quot;&gt;Mention: Currying&lt;/h3&gt;&lt;p&gt;Higher-order functions are also fundamental to &lt;em&gt;partial application&lt;/em&gt; and &lt;em&gt;currying&lt;/em&gt;—two techniques that are irreplaceable in functional programming. Don&amp;#x27;t worry if they sound alien to you, we are going to talk about them in the future chapters of this series.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;putting-it-into-practice&quot;&gt;Putting it into practice&lt;/h2&gt;&lt;p&gt;Just as with any other function, applying the &lt;a href=&quot;/blog/thinking-in-functions&quot;&gt;Input/Output pattern&lt;/a&gt; is a great place to start when writing a higher-order function. With that, there are a few additional questions to ask yourself:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;What action is being delegated to the argument function?&lt;/li&gt;&lt;li&gt;When should the argument function be called?&lt;/li&gt;&lt;li&gt;What data is provided to the argument function?&lt;/li&gt;&lt;li&gt;Does the returned data of the argument function affects the parent function?&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;It&amp;#x27;s crucial to establish a clear separation between the responsibilities of a higher-order function and the argument function it accepts.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Exercise:&lt;/strong&gt; Try to write your own &lt;code&gt;filter()&lt;/code&gt; function: it accepts an array and a function that returns a &lt;code&gt;Boolean&lt;/code&gt;. It returns a new array, with the members for which the argument function returned &lt;code&gt;true&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;showLineNumbers=false&quot; showLineNumbers=&quot;false&quot;&gt;filter([1, 3, 5], (number) =&amp;gt; number &amp;gt; 2) // [3, 5]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Use the &lt;code&gt;map&lt;/code&gt; function we&amp;#x27;ve created earlier in this article as a reference.&lt;/p&gt;&lt;/blockquote&gt;&lt;hr/&gt;&lt;h2 id=&quot;real-life-example&quot;&gt;Real-life example&lt;/h2&gt;&lt;p&gt;While working on one of my projects, I&amp;#x27;ve decided to create a custom function that would allow me to handle an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest&quot;&gt;&lt;code&gt;XMLHttpRequest&lt;/code&gt;&lt;/a&gt; instance as a Promise. The intention was to make such requests declaration shorter and support the &lt;code&gt;async/await&lt;/code&gt; syntax. I&amp;#x27;ve started by creating a helper function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function createXHR(options) {
const req = new XMLHttpRequest()
req.open(options.method, options.url)
return new Promise((resolve, reject) =&amp;gt; {
req.addEventListener(&amp;#x27;load&amp;#x27;, resolve)
req.addEventListener(&amp;#x27;abort&amp;#x27;, reject)
req.addEventListener(&amp;#x27;error&amp;#x27;, reject)
req.send()
})
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I would then use that &lt;code&gt;createXHR&lt;/code&gt; function in my tests like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;test(&amp;#x27;handles an HTTPS GET request&amp;#x27;, async () =&amp;gt; {
const res = await createXHR({
method: &amp;#x27;GET&amp;#x27;,
url: &amp;#x27;https://test.server&amp;#x27;,
})
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Thing is, I also needed to configure the request differently for various testing scenarios: set headers, send request body, or attach event listeners. To support that, I went to my &lt;code&gt;createXHR&lt;/code&gt; function and extended its logic:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=3,5-9,11,15&quot; focusedLines=&quot;3,5-9,11,15&quot;&gt;function createXHR(options) {
const req = new XMLHttpRequest()
req.responseType = options.responseType || &amp;#x27;text&amp;#x27;
if (options?.headers) {
Object.entries(options.headers).forEach([header, value] =&amp;gt; {
req.setRequestHeader(header, value)
})
}
req.addEventListener(&amp;#x27;error&amp;#x27;, options.onError)
return new Promise((resolve, reject) =&amp;gt; {
// ...
req.send(options.body)
})
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As the test scenarios grew in diversity, my &lt;code&gt;createXHR&lt;/code&gt; function grew in complexity. It resulted in an overly complex function that was hard to read and even harder to use. Why did that happen?&lt;/p&gt;&lt;p&gt;My mistake was to assume that the &lt;code&gt;createXHR&lt;/code&gt; function should configure a request on its own. Describing a request configuration as the &lt;code&gt;options&lt;/code&gt; object wasn&amp;#x27;t a sound choice either, since the object is a finite data structure and cannot represent all the variety of how a request can be declared.&lt;/p&gt;&lt;p&gt;Instead, my helper function should have allowed for each individual call to configure a request instance it needs. And it could do that by becoming a higher-order function and accepting an action that configures a request instance as an argument.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=1-2,5-6&quot; focusedLines=&quot;1-2,5-6&quot;&gt;// Accept a `middleware` function,
function createXHR(middleware) {
const req = new XMLHttpRequest()
// ...that configures the given `XMLHttpRequest` instance,
middleware(req)
// ...and still promisifies its execution.
return new Promise((resolve, reject) =&amp;gt; {
req.addEventListener(&amp;#x27;loadend&amp;#x27;, resolve)
req.addEventListener(&amp;#x27;abort&amp;#x27;, reject)
req.addEventListener(&amp;#x27;error&amp;#x27;, reject)
})
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;The reason &lt;code&gt;XMLHttpRequest&lt;/code&gt; instance is declared within the function and not accepted as an argument is because you cannot change certain options once a request has been sent.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Notice how cleaner that function becomes as it delegates the request configuration to a &lt;code&gt;middleware&lt;/code&gt; function. With that, each test can provide its own way to set up a request and still receive a Promise in return.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=3-5,11-13&quot; focusedLines=&quot;3-5,11-13&quot;&gt;test(&amp;#x27;submits a new blog post&amp;#x27;, async () =&amp;gt; {
const req = await createXHR((req) =&amp;gt; {
req.open(&amp;#x27;POST&amp;#x27;, &amp;#x27;/posts&amp;#x27;)
req.setRequestHeader(&amp;#x27;Content-Type&amp;#x27;, &amp;#x27;application/json&amp;#x27;)
req.send(JSON.stringify({ title: &amp;#x27;Thinking in functions&amp;#x27;, part: 2 }))
})
})
test(&amp;#x27;handles error gracefully&amp;#x27;, async () =&amp;gt; {
const req = await createXHR((req) =&amp;gt; {
req.open(&amp;#x27;GET&amp;#x27;, &amp;#x27;/posts/thinking-in-functions&amp;#x27;)
req.addEventListener(&amp;#x27;error&amp;#x27;, handleError)
req.send()
})
})
&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;High-order functions may be a hard concept to grasp at first, but give it some time, apply it in practice, and the understanding will come. It&amp;#x27;s a crucial part of functional programming and a great step towards thinking in functions. I hope this article has contributed to your knowledge, and you feel an extra tool in your arsenal now.&lt;/p&gt;&lt;p&gt;Looking forward to seeing you in the next part of the &amp;quot;Thinking in Functions&amp;quot; series!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to ask questions?]]></title><description><![CDATA[Being a beginner software engineer you are often tasked with implementing real-world features and fixing tricky bugs. Sometimes you may feel…]]></description><link>https://redd.one/how-to-ask-questions</link><guid isPermaLink="false">https://redd.one/how-to-ask-questions</guid><pubDate>Fri, 04 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Being a beginner software engineer you are often tasked with implementing real-world features and fixing tricky bugs. Sometimes you may feel that you lack knowledge or aren&amp;#x27;t sure how to approach the task entirely. That is perfectly fine, even the most experienced engineers suffer from the occassional struggle and nobody truly knows everything, despite how wise they may appear. Asking for help (as well as giving one) is an inseparable part of programming, just as writing code or &lt;a href=&quot;/blog/the-art-of-code-review&quot;&gt;giving code reviews&lt;/a&gt;. However, asking questions is a discipline on its own, and it will take some time to learn how to do that in a way that yields more results with less effort invested.&lt;/p&gt;&lt;p&gt;There are multiple blockers that beginner engineers face when it comes to asking for help. Today I would like to bring some of them up and share my personal experience of how I dealt with those blockers.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;asking-is-learning&quot;&gt;Asking is learning&lt;/h2&gt;&lt;p&gt;I know how it feels to be afraid or ashamed to ask a question, worrying that your colleagues may grade you as less intelligent, frown upon you asking something they find obvious, or even bully you, demanding you should have known the answer by now. If you ever felt like your surrounding doesn&amp;#x27;t encourage questions, &lt;strong&gt;you are in a toxic environment and you need to escape it&lt;/strong&gt;. People who despise interest, experiment, and question, are seldom bright, as they block themselves and others from knowledge. Knowledge comes from learning, and learning comes from asking questions. Make sure you are travelling your journey amongst the people who understand that.&lt;/p&gt;&lt;p&gt;However, if you are shy or insecure when it comes to asking for help, I have a lifehack for you: don&amp;#x27;t treat your question as a sign that you don&amp;#x27;t know something, treat it as a sign that you would like to learn more. Asking a question, in my opinion, is the most efficient way to learn in any field. Doing so advances you in the grand art of asking, and has a bigger chance to remember the answer, as we best remember solutions to the problems we face first-hand.&lt;/p&gt;&lt;div&gt;Do not deprive yourself of the best way to learn: asking a question.&lt;/div&gt;&lt;h2 id=&quot;the-grand-art-of-asking&quot;&gt;The grand art of asking&lt;/h2&gt;&lt;p&gt;The skill of asking the right question is extremely hard to master. Nevertheless, you won&amp;#x27;t get better at it without practicing and keeping track of the details and phrases that got you closer to the answer. This topic deserves a thick book to even scratch the surface, but right now let&amp;#x27;s explore one of the most common issues when asking questions: &lt;em&gt;specificity&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Specificity in questions is often ambiguous, and may bring you both closer and farther away from the answer. Let&amp;#x27;s take a look at why that happens.&lt;/p&gt;&lt;h3 id=&quot;irrelevant-specificity&quot;&gt;Irrelevant specificity&lt;/h3&gt;&lt;p&gt;Here&amp;#x27;s an example of a question:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;How do I persist an authentication token from Google between page refreshes in React?&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;While this may be a valid question, it contains too much details that are not useful when it comes to answering it. For example, instead of binding the authentication process to Google, consider asking about authentication persistency in general, as it&amp;#x27;s going to be vastly the same on your side for Google, Amazon, or any other provider. Instead of scoping the answer to the React ecosystem, try dropping the framework from the question, to learn about the persistency in JavaScript in general. That way the answer you get is more likely to be reused for other frameworks and libraries, making it much more valuable. Lastly, when it comes to persistency, it matters little if you persist an auth token, a unique ID, or any kind of information. Even the &amp;quot;authentication&amp;quot; part of the question can be replaced with &amp;quot;how to persist data&amp;quot;, &lt;em&gt;any&lt;/em&gt; data.&lt;/p&gt;&lt;p&gt;While the context you are working in may be important, dropping it often helps to find the beginning of a thread that will eventually lead you to the right answer.&lt;/p&gt;&lt;div&gt;Look for knowledge, not answers. Knowledge is how the answers are produced.&lt;/div&gt;&lt;p&gt;Once the contextual specificity is omitted, notice how broader and at the same time shorter this question becomes:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;How do I persist data between page refreshes?&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Surely, the kind of answer you get may be incomplete for your particular context, but it will be a complete answer on its own. It may direct you to using cookies, or a session storage in JavaScript to presist arbitrary data. Then, you may want to ask a follow up question or two about using those API in the context of React. Chances are you are not the first person facing this, and there may be a useful article or a library that could help you achieve your goal.&lt;/p&gt;&lt;div&gt;Asking is investigating: you find clues that move you to more specific questions and, eventually, the answer.&lt;/div&gt;&lt;p&gt;When you need to turn a red sphere into a blue pointy cube, don&amp;#x27;t expect to find a solution to that task straight away. Learn how to transform a sphere into cube first, then how to make any cube blue, and then how to make blue things pointy. Any programming task can be broken into a sequence of smaller tasks, researching which will be a more rewarding process in the long run. You may realize you didn&amp;#x27;t even need your cube to be pointy somewhere in the middle of this process.&lt;/p&gt;&lt;p&gt;The reason I highly recommend dropping the context from your question is because it gives you a better chance at finding the answer. This also teaches you that a question rarely has a single direct answer. Get use to starting broad and narrowing the specificity with each piece of knowledge you find. You will be surprised how much you learn that way.&lt;/p&gt;&lt;h3 id=&quot;relevant-specificity&quot;&gt;Relevant specificity&lt;/h3&gt;&lt;p&gt;Including a name of the library, a browser version, or a particular option name is extremely likely to narrow the investigation scope and bring you closer to the answer. There is one important thing in providing such technical specificity: &lt;strong&gt;you must know what is relevant&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Take a look at this question:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Axios fails on a POST request in &lt;code&gt;componendDidMount&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Evaluating the usefulness of those technical details is entirely impossible, as they may be both relevant and irrelevant, depending on the particular use case. It becomes your responsibility to know what to include and what to omit in your question. Truth be told, that is one of the things that makes it difficult to ask the right questions, and one of the skills you acquire with time and practice.&lt;/p&gt;&lt;div&gt;When including technical details, you must know what is relevant and what is not.&lt;/div&gt;&lt;p&gt;Unfortunately, mentioning irrelevant details may lead your investigation effort astray. Fortunately, if the person you asked it familiar with the topic, they may help you filter those details by relevancy.&lt;/p&gt;&lt;p&gt;As the time goes by and you keep asking questions, you will begin to notice how certain technical details shine for you, while others fade away. There is no other master-advice I can give you on this, just to encourage you to keep asking questions!&lt;/p&gt;&lt;h2 id=&quot;the-15-minutes-rule&quot;&gt;The 15 Minutes Rule&lt;/h2&gt;&lt;p&gt;When asking questions one should be careful not to develop a habit of delegating one&amp;#x27;s problems to somebody else. Reaching out for help in a dire situation is a rightful thing to do, yet none of us truly enjoys the struggle, so it&amp;#x27;s natural that we experience satisfaction and joy when the long-awaited help arrives. For that help to last longer try to learn to evaluate the situation before asking, which would prevent occassionally unnecessary questions and give you the sense of standalone accomplishment.&lt;/p&gt;&lt;div&gt;In the end, expect the quality of the answer to be proportionate to the time you were willing to spend finding it.&lt;/div&gt;&lt;p&gt;What helped me during my career is adopting the 15 minutes rule. Whenever I find myself in a struggle, I dedicate 15 minutes of highly focused time to solve the matter at hand by myself. By the end of that time I check if the question I had is still relevant, and if it&amp;#x27;s not I tackle the next question that arose with another 15 minutes. Only when my effort proves unfruitful I raise a question to a colleague or a fellow developer.&lt;/p&gt;&lt;p&gt;This rule is extremely useful, because it brings value to your question. Often you may learn a thing or two while attempting to solve the issue, if not find the answer. The knowledge you acquire and the approaches you try may help your future advisor to tackle your question more precisely, and the effort you put into looking into it acts as a solid foundation for compassionate and emphathetic response.&lt;/p&gt;&lt;h2 id=&quot;be-patient&quot;&gt;Be patient&lt;/h2&gt;&lt;p&gt;Software engineering can be stressful: a customer losing their revenue due to an issue, a manager enraged over a missed deadline—we&amp;#x27;ve all been there. Getting stressed doesn&amp;#x27;t really solve those situations, neither will it find the answer to your question.&lt;/p&gt;&lt;p&gt;Remember that explaining is equally difficult as asking the right question, and a person that put down their time to help you may struggle to find the right way to bring it to you. Patience and kindness go a long way.&lt;/p&gt;&lt;h2 id=&quot;help-others&quot;&gt;Help others&lt;/h2&gt;&lt;p&gt;No matter how inexperienced you may think you are, there is always a person one step behind you. That person is following a similar journey, but may not have got their head around certain concepts or patterns as you did. Keep that in mind next time you see somebody confused over a topic you find utterly clear. Offer them help.&lt;/p&gt;&lt;div&gt;Offering help is just as useful as asking for one.&lt;/div&gt;&lt;p&gt;Altruism isn&amp;#x27;t the only reason to offer others help. Explaining is perhaps the best way to ensure your understanding. As we all think and remember things differently, at first it will be challenging to broadcast your thoughts to the person in need. Sometimes it would require you to validate the material you&amp;#x27;ve learned, lest you give a false or incomplete answer. It may help you locate a few blind spots in your own knowledge!&lt;/p&gt;&lt;h2 id=&quot;draw-a-conclusion&quot;&gt;Draw a conclusion&lt;/h2&gt;&lt;p&gt;Getting the answer is sure thing rewarding, yet try to keep it in your head for a while. A problem isn&amp;#x27;t a burning furnace, and throwing one answer after another won&amp;#x27;t do any good, as the purpose of asking is to learn. Getting a valuable information from the answer may require to look back at the original problem and draw a conclusion. What caused the problem? What was the solution? Did the solution affect the cause directly or indirectly? Is there any supplimentary knowledge that could have helped in solving this problem?&lt;/p&gt;&lt;p&gt;Believe me or not, learning is a skill of its own, as we all learn, remember, and explain things in a drastically different manner. So while at it, why not to boost another skill when the answer is already in your hands? Experiment and find the way of learning that works the best for you, be it drawing a diagram, writing a small documentation, or hoarding useful links in your bookmarks. Next time a question pops out, your learning vault is a great place to start with the 15 minutes rule!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Plague of Linters]]></title><description><![CDATA[You have a project and you want to maintain its code style, prevent typos and other mistakes to land in your repository, and get suggestions…]]></description><link>https://redd.one/the-plague-of-linters</link><guid isPermaLink="false">https://redd.one/the-plague-of-linters</guid><pubDate>Fri, 10 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You have a project and you want to maintain its code style, prevent typos and other mistakes to land in your repository, and get suggestions that can occassionally improve your application&amp;#x27;s logic. Most likely, you use linters for that task.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Linter is a program that analyzes your code against a set of defined rules. One of the most popular linters in JavaScript is &lt;a href=&quot;https://eslint.org&quot;&gt;ESLint&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;With tools like JSHint, JSLint and ESLint, checking (linting) our JavaScript code became possible and widespread. Unfortunately, its popularity was followed by what I call the Plague of Linters.&lt;/p&gt;&lt;p&gt;Today I would like to discuss with you the purpose of linting, go through the most common mistakes people make when setting up a linter in their projects, and spread a few thoughts that I wish were more present in our ever-evolving ecosystem.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;if-you-like-it-then-you-shoulda-ctrlc-on-it&quot;&gt;If you like it, then you shoulda &lt;code&gt;Ctrl+C&lt;/code&gt; on it&lt;/h2&gt;&lt;p&gt;The biggest and most dreadiest mistake developers make when introducing and maintaining their linter is copy-pasting a linting configuration from another projects. Let me elaborate.&lt;/p&gt;&lt;p&gt;Imagine you&amp;#x27;re bootstrapping a new app, and you want to have code consistency and basic quality assurance at place. You reach for your favorite linter and, while installing it, your fingers subconsciously type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ npm install eslint-config-airbnb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&amp;quot;Well, why shouldn&amp;#x27;t I use AirBnB&amp;#x27;s linting setup?&amp;quot;&lt;/em&gt; you stop for a moment, &lt;em&gt;&amp;quot;They are a world-known successful company, they must know something about code quality, and I want to use their experience!&amp;quot;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;There is one issue, though: you are not AirBnB. Adopting their linter configuration doesn&amp;#x27;t make you AirBnB, and doesn&amp;#x27;t automatically make your code better and your project successful. All you are doing by using their configuration is blindly copying the result of their journey without taking one of your own.&lt;/p&gt;&lt;p&gt;Settle with the idea that each project may require a different set of linting rules, and that&amp;#x27;s perfectly fine. Moreover, it&amp;#x27;s much more preferable and thoughtful than copy-pasting code in general.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;choose-what-works-best-for-you&quot;&gt;Choose what works best for you&lt;/h2&gt;&lt;p&gt;What I&amp;#x27;m about to say may bring you into a state of shock, but bear with me:&lt;/p&gt;&lt;div&gt;Go through &lt;u&gt;each&lt;/u&gt; ESLint rule and configure it so it works for &lt;u&gt;your&lt;/u&gt; team.&lt;/div&gt;&lt;p&gt;&lt;em&gt;&amp;quot;What?! That&amp;#x27;s going to take forever! Why should I waste my time on this?&amp;quot;&lt;/em&gt; I hear you exclaim in fury. But wait, you already spend time on configuring your CI pipeline, API clients, or state management. Why do you think that spending time on a tool that ensures the overall code consistency is not worth of your time? Having a set of rules that support your code style is an absolute must to establish a solid and pleasant developer experience.&lt;/p&gt;&lt;p&gt;If time is your main concern, organize this as a team activity. In the end, all developers will be using these rules on a daily basis, so they should participate in deciding what would suit them. Overall, &lt;a href=&quot;https://eslint.org/docs/rules/&quot;&gt;revisiting each rule&lt;/a&gt; and making a choice won&amp;#x27;t take you more than 1 hour, but will inevitably ensure your developer comfort and code consistency for years.&lt;/p&gt;&lt;p&gt;The best part is that once you&amp;#x27;re settled on the rules, you can try them out and abstract those that work into your own ESLint configuration (just like AirBnb did!). This would give you a solid linting foundation and minimize the bootstrapping time of the next project. You don&amp;#x27;t really want to adopt dramatically different coding style across projects, as that would be against the concistency that you strive towards.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;its-not-written-in-stone&quot;&gt;It&amp;#x27;s not written in stone&lt;/h2&gt;&lt;p&gt;Here&amp;#x27;s another totally crazy statement:&lt;/p&gt;&lt;div&gt;If a linting rule doesn&amp;#x27;t help you—remove it.&lt;/div&gt;&lt;p&gt;The only purpose of a linter is to make your life as a developer easier and help you develop with confidence. However, in reality it&amp;#x27;s often the contrary: developers treat their linting rules like a law, no matter how obscure, rejoicing that it can be occassionally broken with &lt;code&gt;/* eslint-disable */&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The rules you configure are for you. If you, or your team, struggle because of a certain linting rule, discuss it and disable it, if necessary. You are &lt;strong&gt;not&lt;/strong&gt; compromising on code&amp;#x27;s quality by disabling linting rules. The harm an obscure rule can do is tenfold higher than any implications of removing it.&lt;/p&gt;&lt;p&gt;Staying objective and respectful of your team&amp;#x27;s previous choices is crucial when discussing linting rules that you struggle with. Certain decisions have a history behind them, and you can get used to them (or even adopt them yourself) through time. That being said, if you do struggle, voice that and provide some meaningful arguments to why you find a specific rule obstructing in your work.&lt;/p&gt;&lt;div&gt;Developers don&amp;#x27;t have time to think about user experience if their developer experience suffers.&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;This point is especially critical when there is no code formatting tool to support your stylistic choices. The fact that I need to think whether I&amp;#x27;ve added a semicolon, removed a trailing comma, or forgot to put a shorthand object property first—that&amp;#x27;s a time waste. This time can be spent on getting a linting config that works, or even better—getting the work done.&lt;/p&gt;&lt;p&gt;Generally, I highly recommend using tools like &lt;a href=&quot;https://prettier.io&quot;&gt;Prettier&lt;/a&gt; to format your code and forget about stylistic choices altogether. It will feel weird at first, but you will thank me later.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Try to understand and encourage that linting is something configured by you, maintained by you, and aimed to help you. There are no such things as conventions, good, or bad practices in the automatic code checks. There are no bad decisions in linting rules, apart from those blindly copied and those that obscure and block your working process.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;iterate-iterate&quot;&gt;Iterate, iterate!&lt;/h2&gt;&lt;p&gt;Imagine that one day you&amp;#x27;ve decided to start brewing coffee at home. You had no idea how to do that, so you looked up some articles, watched some videos, and the overall idea of the brewing process has shapened up in your head. Although you wouldn&amp;#x27;t call the first few times ideal, you can feel how it slowly begins to click for you with each next try. A couple of months pass and you can make your favorite espresso in a matter of minutes, almost effortless! You also noticed that you&amp;#x27;ve slightly adjusted the initial process to your specific needs. That adjustment, that adaptation, is an important part of any learning process.&lt;/p&gt;&lt;p&gt;There&amp;#x27;s a notion amongst developers that a linting configuration is something written once and never touched again. I don&amp;#x27;t know where it comes from, but let my demystify it once and for all:&lt;/p&gt;&lt;div&gt;Linting rules is about your choice. Your choices change as you evolve. Let your linter reflect that.&lt;/div&gt;&lt;p&gt;It is only by iterating over your configuration that you prove your choices with time, and filter out those that are not relevant anymore, or didn&amp;#x27;t last. As a developer, don&amp;#x27;t hesitate to express your unease with linting. Remember, linter is here to help you, not the other way around.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Configure a set of rules that work for your/your team;&lt;/li&gt;&lt;li&gt;Iterate on your config: replace or remove rules that block the process, or add the rules that could&amp;#x27;ve saved you;&lt;/li&gt;&lt;li&gt;Isolate common rules into your custom configuration preset;&lt;/li&gt;&lt;li&gt;Set up &lt;code&gt;pre-commit&lt;/code&gt;/&lt;code&gt;pre-push&lt;/code&gt; Git hooks to lint your code automatically (invalid code must never be committed to Git);&lt;/li&gt;&lt;li&gt;Get a feedback from your team on the linting setup.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;Thanks for reading through this article! I hope I managed to show you how important it is to treat your linting setup a something you have control over. Share these tips with your team and have a great developer experience when working on amazing products.&lt;/p&gt;&lt;p&gt;Let me know on &lt;a href=&quot;https://twitter.com/kettanaito&quot;&gt;Twitter&lt;/a&gt; if you would like to read more insights on efficient linting setup, or how to use Git hooks to bring linting to the next level. I&amp;#x27;d love to know if this resonates with you. Thanks.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building a tree-shakable library with RollupJS]]></title><description><![CDATA[There are multiple reason to pick RollupJS as your bundler of choice when creating a JavaScript library: a minimalistic configuration…]]></description><link>https://redd.one/building-a-tree-shakable-library-with-rollupjs</link><guid isPermaLink="false">https://redd.one/building-a-tree-shakable-library-with-rollupjs</guid><pubDate>Sun, 28 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There are multiple reason to pick &lt;a href=&quot;https://rollupjs.org&quot;&gt;RollupJS&lt;/a&gt; as your bundler of choice when creating a JavaScript library: a minimalistic configuration, built-in way to compile into various formats, and extensive tree-shaking support. Today we are going to look at tree-shaking from the perspective of a library author, rathen than a consumer.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;what-is-tree-shaking&quot;&gt;What is tree-shaking?&lt;/h2&gt;&lt;p&gt;&lt;em&gt;Tree-shaking&lt;/em&gt; (also known as &lt;em&gt;dead code elimination&lt;/em&gt;) is a bundling technique that allows to remove an unused code from the build. The unused code is the one nothing else depends on. That dependency relation is determined by a bundler/compiler on build time, making tree-shaking a build technique.&lt;/p&gt;&lt;p&gt;Tree-shaking is often approached on a consumer&amp;#x27;s level, meaning that our &lt;em&gt;built application&lt;/em&gt; does not ship an unused code. While this is a perfectly valid concern, those third-party tools (i.e. open source libraries) we use must support tree-shaking as well, otherwise there&amp;#x27;s nothing we can do about a dead code.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;This article will refer to any JavaScript application using a library as a &lt;em&gt;consumer&lt;/em&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;h3 id=&quot;what-do-we-want&quot;&gt;What do we want?&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s say we are writing a library called &lt;code&gt;my-lib&lt;/code&gt; that exports two functions: &lt;code&gt;funcA&lt;/code&gt; and &lt;code&gt;funcB&lt;/code&gt;. Neither of those can be tree-shaken when building our library, because we don&amp;#x27;t know which functions the constumer will require. However, we need to bundle our library the way that everything that the consumer doesn&amp;#x27;t use gets dropped.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s an example of what we expect from the source and built code of the consumer:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// consumer/src/index.js
// Although &amp;quot;my-lib&amp;quot; exports both &amp;quot;funcA&amp;quot; and &amp;quot;funcB&amp;quot;,
// since we only rely on &amp;quot;funcA&amp;quot;, we expect &amp;quot;funcB&amp;quot;
// to get tree-shaken (removed) from our application&amp;#x27;s build.
import { funcA } from &amp;#x27;my-lib&amp;#x27;
funcA()
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// consumer/build/index.js
;(function (factory) {
typeof define === &amp;#x27;function&amp;#x27; &amp;amp;&amp;amp; define.amd ? define(factory) : factory()
})(function () {
&amp;#x27;use strict&amp;#x27;
function funcA() {
console.log(&amp;#x27;Hello from the module A!&amp;#x27;)
}
funcA()
})
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Notice how the &lt;code&gt;funcB&lt;/code&gt; is nowhere to be found in the application&amp;#x27;s build. &lt;em&gt;That&lt;/em&gt; is our end goal.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;As natural as it may look, this is not the default behavior, and it requires a certain setup on both the library&amp;#x27;s and consumer&amp;#x27;s sides. So what do we, library authors, need to do in order to ship a tree-shaking support to our users?&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;build-format&quot;&gt;Build format&lt;/h2&gt;&lt;p&gt;First of all, we need to bundle our library using a proper build format.&lt;/p&gt;&lt;p&gt;Choosing a proper format is crucial, because certain formats make our library&amp;#x27;s dependencies impossible to statically analyze. For example, if we ship a library in a CommonJS format, a consumer won&amp;#x27;t be able to figure out which modules can be tree-shaken, because their dependnecies may change on runtime.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// my-lib/build/cjs/index.js
function funcA(moduleName) {
// Impossible to analyze what &amp;quot;funcA&amp;quot; depends on,
// because the &amp;quot;require&amp;quot; statement below will change
// depending on the &amp;quot;moduleName&amp;quot; argument.
require(`./utils/${moduleName}`)
}
module.exports = { funcA }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When a bundler (i.e. webpack or Rollup) transforms our library&amp;#x27;s code, it must meet &lt;em&gt;static import statements&lt;/em&gt; (&lt;code&gt;import&lt;/code&gt;) to determine which modules can be safely removed. Static import statements are such that cannot change on runtime (thus, &amp;quot;static&amp;quot;).&lt;/p&gt;&lt;div&gt;In order for our library to be tree-shakable, it must preserve static import statements.&lt;/div&gt;&lt;p&gt;To preserve those imports we need to distribute our library using &lt;strong&gt;ESM&lt;/strong&gt; format. ESM (ECMAScript Module) format comes with a &lt;a href=&quot;https://exploringjs.com/es6/ch_modules.html#static-module-structure&quot;&gt;static module structure&lt;/a&gt;, which means that a module&amp;#x27;s dependencies can be determined by looking at the code, without having to run it.&lt;/p&gt;&lt;p&gt;We can use a &lt;code&gt;format&lt;/code&gt; output option in the Rollup configuration to build our library into ESM:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=6,7&quot; focusedLines=&quot;6,7&quot;&gt;// my-lib/rollup.config.js
module.exports = {
// ...,
output: [
{
dir: &amp;#x27;library/build&amp;#x27;,
format: &amp;#x27;esm&amp;#x27;,
},
],
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, a right format is not enough for our library to become fully tree-shakable. We also need to specify a proper relation between the modules in our library by configuring its entry points.&lt;/p&gt;&lt;h2 id=&quot;entry-points&quot;&gt;Entry points&lt;/h2&gt;&lt;p&gt;It&amp;#x27;s often for a code to be reused between the parts of a library. If such code comes from a module that you would want to tree-shake, it may get problematic. Consider this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// my-lib/src/a.js
// Here we are importing a helper function from the module B,
// making it a dependency of the module A (current module).
import { print } from &amp;#x27;./b&amp;#x27;
export function funcA() {
print(&amp;#x27;Hello from module A!&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// my-lib/src/b.js
// A helper function that semantically belongs to module B,
// but can be imported and used in other modules as well.
export function print(message) {
console.log(message)
}
export const funcB = () =&amp;gt; {
print(&amp;#x27;Hello from module B!&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the &lt;code&gt;a.js&lt;/code&gt; module importing the &lt;code&gt;print&lt;/code&gt; function from &lt;code&gt;b.js&lt;/code&gt;, the latter effectively becomes its dependency. Now, even if our consumer doesn&amp;#x27;t use &lt;code&gt;funcB&lt;/code&gt;, they would still import and ship the entire &lt;code&gt;b.js&lt;/code&gt;, because &lt;code&gt;a.js&lt;/code&gt; relies on its helper function.&lt;/p&gt;&lt;p&gt;Surely, we can isolate the &lt;code&gt;print&lt;/code&gt; function into its own module and reuse it between any other pieces of the library. However, this delegates dependency management into our hands, which makes it prone to mistakes. Instead, we can configure our library&amp;#x27;s entry points, so that Rollup figures out the cross-module dependencies for us.&lt;/p&gt;&lt;p&gt;To set multiple entries we need to provide a Rollup config with the &lt;code&gt;input&lt;/code&gt; option that has a list of modules.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=3&quot; focusedLines=&quot;3&quot;&gt;// my-lib/rollup.config.js
module.exports = {
input: [&amp;#x27;src/index.js&amp;#x27;, &amp;#x27;src/a.js&amp;#x27;, &amp;#x27;src/b.js&amp;#x27;],
// ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Such build configuration will not only generate separate chunks for each given input, but can also figure out their dependency between each other, putting a shared code into common chunks.&lt;/p&gt;&lt;p&gt;With the entry points configured, our build folder structure may look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; metastring=&quot;focusedLines=4&quot; focusedLines=&quot;4&quot;&gt;└─build
└─esm
├─a.js
├─a-deps.js # `print` from `src/b.js` would be here
└─b.js
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, if the &lt;code&gt;b.js&lt;/code&gt; module is never used by the consumer, it will get tree-shaken, because neither consumer, nor internal library&amp;#x27;s modules rely on it.&lt;/p&gt;&lt;h2 id=&quot;distribution&quot;&gt;Distribution&lt;/h2&gt;&lt;p&gt;Although ESM is the future of the modules distribution, we are not quite ready for that future yet. With that in mind, it&amp;#x27;s not a good decision to specify the ESM bundle as the &lt;code&gt;main&lt;/code&gt; entry of your package. Instead, there&amp;#x27;s a dedicated &lt;code&gt;module&lt;/code&gt; property in &lt;code&gt;package.json&lt;/code&gt; that modern tools, like webpack and Rollup, can pick up and use.&lt;/p&gt;&lt;p&gt;Provide the path to your ESM build in the &lt;code&gt;module&lt;/code&gt; property of your library&amp;#x27;s &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; metastring=&quot;focusedLines=7&quot; focusedLines=&quot;7&quot;&gt;// my-lib/package.json
{
// CommonJS build as default
&amp;quot;main&amp;quot;: &amp;quot;lib/cjs/index.js&amp;quot;,
// ESM build for modern bundlers
&amp;quot;module&amp;quot;: &amp;quot;lib/esm/index.js&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;&lt;p&gt;Building a tree-shakable library with RollupJS consists of these steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Choose a proper build target format (ESM);&lt;/li&gt;&lt;li&gt;Specify the library&amp;#x27;s entry points (to analyze their inter-dependencies);&lt;/li&gt;&lt;li&gt;Provide the ESM build path as the &lt;code&gt;module&lt;/code&gt; property of the library&amp;#x27;s &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Below you can find a GitHub repository that contains a library setup and the example of a consumer application that is using that library. Follow the instructions in this repository to see the tree-shaking for yourself:&lt;/p&gt;&lt;div owner=&quot;Redd-Developer&quot; repo=&quot;rollup-tree-shakable-library&quot;&gt;&lt;/div&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;Thanks for reading through! I hope you&amp;#x27;ve learned a thing of two about JS modules and tree-shaking, and wish you to ship awesome libraries to your users! Let me know on Twitter about your experience with creating and shipping libraries.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Efficient CircleCI debugging with SSH]]></title><description><![CDATA[Introduction I have been actively using CircleCI in my projects for years now and I love it! However, I'm ashamed to admit that most of…]]></description><link>https://redd.one/efficient-circleci-debugging-with-ssh</link><guid isPermaLink="false">https://redd.one/efficient-circleci-debugging-with-ssh</guid><pubDate>Mon, 11 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;&lt;p&gt;I have been actively using &lt;a href=&quot;https://circleci.com/&quot;&gt;CircleCI&lt;/a&gt; in my projects for years now and I love it! However, I&amp;#x27;m ashamed to admit that most of the failed jobs took me up to 1 hour to get right. By the time I saw CI pass, I had grew an unrivaled hatred towards the red color.&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1200px&quot;&gt;
&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1acec2762e974b679d3f326111325338/7575b/ci-failed.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
&lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:69.66666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAB3ElEQVQ4y5WSyU7kQBBE/WUIsd7hxPoBSHwfMwckNkEfQKyH4dRST/c0HHpxlfctJqKMGTPISFh6yrQ7MjIrq70qCJA/PtY8PdUov7lBfn+P/Pa25u7uY97Wi4cHlL4PL+MP/upqzdpaHdfXERwcwGxuwu7twe7u1uzvE77v7DhNU2OULy4iPTujIZ3N8jLsygosBS5SFB0eItjYQEiTUKZbW7W52N6GT52/tASftUb5wgLS01N4pTGIez2El5ewFxd1PD+HOT5GwI725AQhY05NfnVVo/z6+gMJvxeTCbwKQE6CosArdzCNYxf/zOd4UZzNMIkiSNf1lCSqKqfxyrJEHIYIOGnCwoiXpHdQ0KbM804qDiNK4lUUG5qNRiOMx2MMh0P0+32o0Xce+QhPLz+PfqCnvbDbjEeMOGnIKZWrmc+jK59zDYrWWlfcafj86xmDwcB9kFGapo6Y+0ySxKFcjRSzLPu3P56kjdccTSJNoO6i8wKoV7PpdOrM29M5QyUpzWJ2l2jCqxeNqI0KCi5ehobNszT5bNiIMt3Sf5O0UYF0Tk+CvICfZq6uMXufsOBl2IQ7owhvP3ZNqIuLaTSyIX6bEIlqNED7yM7grYOm+Ipmkvf/Z6tGXn8BdNwKKMedoeEAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
&lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;CircleCI failed job&quot; title=&quot;CircleCI failed job&quot; src=&quot;/static/1acec2762e974b679d3f326111325338/c1b63/ci-failed.png&quot; srcSet=&quot;/static/1acec2762e974b679d3f326111325338/5a46d/ci-failed.png 300w,/static/1acec2762e974b679d3f326111325338/0a47e/ci-failed.png 600w,/static/1acec2762e974b679d3f326111325338/c1b63/ci-failed.png 1200w,/static/1acec2762e974b679d3f326111325338/7575b/ci-failed.png 1608w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
&lt;/a&gt;
&lt;/span&gt;&lt;/p&gt;&lt;p&gt;In this post I would like to share a mind-blowing technique of debugging failed CircleCI jobs suggested by &lt;a href=&quot;https://twitter.com/FelicianoTech&quot;&gt;Ricardo Feliciano&lt;/a&gt;. As the time passed, I have adopted this technique even outside of CI, but I believe there are developers who are as alien to this approach just as I once was. I will also add a few personal tricks and tips to make the most out of this topic. Let&amp;#x27;s learn how to resolve failed jobs in a fast and efficient manner.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;reproducing-an-issue&quot;&gt;Reproducing an issue&lt;/h2&gt;&lt;p&gt;Reliable issue verification is the absolute must in order to resolve any problem. We follow steps not only to reproduce an issue, but also to verify when it&amp;#x27;s been successfully fixed. There is a myriad of things that can affect an issue&amp;#x27;s reproduction: environment, connection speed, asynchronicity, dark magic. When your CI runs remotely, all of those factors are, inevitably, at play.&lt;/p&gt;&lt;div&gt;The issue that cannot be reproduced cannot be fixed.&lt;/div&gt;&lt;p&gt;Thus, whenever a remote job fails, it&amp;#x27;s in our best interest to match the context in witch it failed as close as possible to provide a viable fix. Gladly, there is a way to connect to the very machine that&amp;#x27;s running our job and explore its state. Enter SSH.&lt;/p&gt;&lt;h2 id=&quot;ss-what&quot;&gt;SS-what?&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Secure Shell&lt;/strong&gt; (also SSH) is a network protocol that encrypts data packages sent over an insecure connection. SSH establishes a tunnel between a verified client and a server, encrypting any data that flow in-between.&lt;/p&gt;&lt;p&gt;Originally designed by &lt;a href=&quot;https://twitter.com/tjssh&quot;&gt;Tatu Ylönen&lt;/a&gt; in Helsinki University of Technology, SSH has been widely adopted, gradually becoming a standard in software engineering. Most of the websites you visit today use SSH for various purposes, like sendind and acceptind traffic, or transfering files.&lt;/p&gt;&lt;p&gt;In this tutorial we are primarily interested in these applications of SSH:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Logging to a shell on a remote host (machine);&lt;/li&gt;&lt;li&gt;Transferring files.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;ssh-setup&quot;&gt;SSH setup&lt;/h2&gt;&lt;p&gt;Connecting to a remote host via SSH is like openning a door: one requires a key. An SSH key allows a remote machine to recognize a connecting host and decide whether to grant access. For the purpose of this tutorial I presume that your CircleCI is connected with your &lt;strong&gt;GitHub acount&lt;/strong&gt;. This means that GitHub will be responsible for providing your SSH key to CI.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;You can skip this section if you are familiar with SSH keys and have an SSH key generated and connected to your CircleCI account.&lt;/p&gt;&lt;/blockquote&gt;&lt;h3 id=&quot;check-existing-ssh-keys&quot;&gt;Check existing SSH keys&lt;/h3&gt;&lt;p&gt;First, check if you don&amp;#x27;t have any SSH keys already on the &lt;a href=&quot;https://github.com/settings/keys&quot;&gt;SSH and GPG keys&lt;/a&gt; page in your GitHub account. In case you do, you should see the list of your active SSH keys:&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1200px&quot;&gt;
&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f7f580a52882346bd701a0324e8f5b07/d2433/github-ssh-keys.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
&lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:17.333333333333336%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAgElEQVQI13WMywrCMBRE+/9f6MaNirRN0OadNvf2KLG40oHDwDAzwyYbp8eZycxcrjfu44ixFuc9MSZSSoQYCSGQS+GXVBU/W2ouDK01allxyTM5w7QYjLM880IqmcV5al2/433ffyJbQ0QY5P3eA5UD7TT5FFqT7nrk+o/+obwAY2zqXoni+cYAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
&lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;GitHub SSH keys list&quot; title=&quot;GitHub SSH keys list&quot; src=&quot;/static/f7f580a52882346bd701a0324e8f5b07/c1b63/github-ssh-keys.png&quot; srcSet=&quot;/static/f7f580a52882346bd701a0324e8f5b07/5a46d/github-ssh-keys.png 300w,/static/f7f580a52882346bd701a0324e8f5b07/0a47e/github-ssh-keys.png 600w,/static/f7f580a52882346bd701a0324e8f5b07/c1b63/github-ssh-keys.png 1200w,/static/f7f580a52882346bd701a0324e8f5b07/d61c2/github-ssh-keys.png 1800w,/static/f7f580a52882346bd701a0324e8f5b07/97a96/github-ssh-keys.png 2400w,/static/f7f580a52882346bd701a0324e8f5b07/d2433/github-ssh-keys.png 2600w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
&lt;/a&gt;
&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Alternatively, check for any existing SSH keys on your local machine by running:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ls -la ~/.ssh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Running the command above will list the available SSH keys, if any. SSH keys file names usually look like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;id_rsa.pub
id_ecdsa.pub
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In case you don&amp;#x27;t have any SSH keys, or would like to create a new one for the sake of this tutorial, please follow the instructions below.&lt;/p&gt;&lt;h3 id=&quot;cretate-new-ssh-key&quot;&gt;Cretate new SSH key&lt;/h3&gt;&lt;p&gt;First, let&amp;#x27;s generate a new SSH key:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh-keygen -t rsa -b 4096 -C &amp;quot;your_email@domain.com&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Use your GitHub user email.&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-t&lt;/code&gt;, a type of a key to create (&lt;code&gt;dsa&lt;/code&gt;, &lt;code&gt;ecdsa&lt;/code&gt;, &lt;code&gt;ed25519&lt;/code&gt;, &lt;code&gt;rsa&lt;/code&gt;);&lt;/li&gt;&lt;li&gt;&lt;code&gt;-b&lt;/code&gt;, the number of bits in the key (for the &lt;code&gt;rsa&lt;/code&gt; key type should be greater than &lt;code&gt;768&lt;/code&gt;);&lt;/li&gt;&lt;li&gt;&lt;code&gt;-C&lt;/code&gt;, a key comment. The value of this comment is an email that is going to be validated to identify your connections.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Running the &lt;code&gt;ssh-keygen&lt;/code&gt; command above will ask you where to save a newly created SSH key, and you can choose the default option, which would be in the &lt;code&gt;~/.ssh&lt;/code&gt; directory. You may also choose to encrypt your new SSH key with a pass-pharse, just bear in mind you would have to enter it each time you use that key.&lt;/p&gt;&lt;p&gt;Verify that a new SSH key has been created by running the following command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ls -la ~/.ssh
# Should return the list of files that includes your newly created SSH key:
#
# -rw------- 1 username staff 1234 Feb 1 2020 id_rsa
# -rw-r--r-- 1 username staff 1234 Feb 1 2020 id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;add-ssh-key-to-ssh-agent&quot;&gt;Add SSH key to ssh-agent&lt;/h3&gt;&lt;p&gt;Regardless of which SSH key you&amp;#x27;ve decided to use, it must be added to yout ssh-agent. Think of SSH agent as a keychain that stores your SSH keys.&lt;/p&gt;&lt;p&gt;Start the agent in the background:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ eval &amp;quot;$(ssh-agent -s)&amp;quot;
# Agent pid 12345
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Modify your &lt;code&gt;~/.ssh/config&lt;/code&gt; file by adding the following section:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Host *
AddKeysToAgent yes
UseKeychain yes
# Point to the generated SSH key
IdentityFile ~/.ssh/id_rsa
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Lastly, add the SSH key to the agent:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh-add -K ~/.ssh/id_rsa
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-K&lt;/code&gt;, is a MacOS-specific option that tells ssh-agent to store your key in the keychain. Skip this option when using a different OS.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Verify that you have got your SSH key loaded to the agent by running this command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh-add -l
# 4096 SHA256:03eb924754b981b2aed90b699ff513cb0b6f4f93c35 your.email@host.com (RSA)
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;add-ssh-key-to-github&quot;&gt;Add SSH key to GitHub&lt;/h3&gt;&lt;p&gt;Please follow the official instructions on &lt;a href=&quot;https://help.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account&quot;&gt;Adding a new SSH key to your GitHub account&lt;/a&gt;. Once successful, you should see the SSH key in your &lt;a href=&quot;https://github.com/settings/keys&quot;&gt;SSH and GPG keys&lt;/a&gt; page.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;running-circleci-job-with-ssh&quot;&gt;Running CircleCI job with SSH&lt;/h2&gt;&lt;p&gt;Once our SSH setup is done, let&amp;#x27;s switch to the CI part. In order to ssh into a remote host responsible for a particular job, that job must be run with SSH first. You can do that directly from the CircleCI UI by following these instructions:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Open a failed job&amp;#x27;s detail page.&lt;/li&gt;&lt;li&gt;Locate the &amp;quot;&lt;strong&gt;Rerun workflow&lt;/strong&gt;&amp;quot; button.&lt;/li&gt;&lt;li&gt;Open the dropdown and choose &amp;quot;&lt;strong&gt;Rerun job with SSH&lt;/strong&gt;&amp;quot; option.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1200px&quot;&gt;
&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/213fb48c6f4fd5155ea04451f9bfc5e3/ac25d/circleci-rerun-ssh.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
&lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:39.666666666666664%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABkklEQVQoz4WPy05aURSGz2tAuISBQLiMSJg1+gReJoxNDU8kktoYrQ+giTisMe2wjZREbodboXCOEBA8nHPAQUk+995RB+2AwZe117///a+1tUAgwL/4/X5CoRCZTIZIJILP5/vPEwwG8Xg87Ozt8mFrE6/Xy0Z4Ay0WixGPx5H17SxJJpNks1nS6TTRaFRpiUTi3Sfvw+Ew+wcf2d7bUYNTqRRas9mkWq0ia6PRoFarqV6eS6USlUrlXS+Xy+i6rpC61Ip3RX4Vi8rTarXQbNtWYaZp0u12qdfrqg4GAzqdjno0Ho8ZjUa0221msxnT6ZRer0e/31e69E0mE1arFZo0PxgGv4W5LYJbDR1LPNJFUOX+nj8ifPb4yLPrshQsHIeFWGL52i9FL6s7n+MIXTOGQ+piUlNsqMtgMcAUl4Y1ZyQDAFfgrEF63L9yw1qV4fk55skJRu4QM5djmM/zkD9ifPwJ6/QU62wNX86wPh9j396iPYkvLb5/w735inN9LShgv1EoMC9crUX5Li9wfv7gBY1K5lgMD5XSAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
&lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Rerun job with SSH&quot; title=&quot;Rerun job with SSH&quot; src=&quot;/static/213fb48c6f4fd5155ea04451f9bfc5e3/c1b63/circleci-rerun-ssh.png&quot; srcSet=&quot;/static/213fb48c6f4fd5155ea04451f9bfc5e3/5a46d/circleci-rerun-ssh.png 300w,/static/213fb48c6f4fd5155ea04451f9bfc5e3/0a47e/circleci-rerun-ssh.png 600w,/static/213fb48c6f4fd5155ea04451f9bfc5e3/c1b63/circleci-rerun-ssh.png 1200w,/static/213fb48c6f4fd5155ea04451f9bfc5e3/d61c2/circleci-rerun-ssh.png 1800w,/static/213fb48c6f4fd5155ea04451f9bfc5e3/ac25d/circleci-rerun-ssh.png 2342w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
&lt;/a&gt;
&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Clicking on the option will run the respective job anew, yet this time CircleCI will issue an SSH connection to the remote machine. You can notice that a new step has been added that describes details of that connection:&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1200px&quot;&gt;
&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2e05b31bec3d7c2c5d15cf400327822a/d4e7f/ci-enable-ssh.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
&lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:47.66666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAByklEQVQoz52Ry27aUBCGvWfByhUgbibFF2x8wbZCMJA9NkQV6iOhhotUVd2DgBfgRXiMhA1quAhDFKE/nkOFsqiqqpY+zZmL5/xzhns7vuLp+Rmr1Qrb7Rb/853PZ6zXa4RhCO71cMSvlxdsNhvs93scj8e/EoYXPsYOhwN2ux1OpxO4r1+68AMf7XY7IkCn04bvtyL8K61WC0EQ4OGhc4V8qqP/ut0ums0mFosFOPmziKJYhGkaKJUUOE4Ful6GZZnMEooiI5/PQxDyuLkpMMjP5XJRTIAkiYjH45hMJuBMrcwaKooCWZZZU01ToaolFiOfEEXxA0VmqV6SpChfQjKZxGw2A+fdVmGYeqTMwf19I1JmoVzWWFNqpKrqH6E8qdc0DZWKxRpOp1NwtmExhXQjKSJ7GU9goxUKl/Gy2SzS6XRE5jdpZDIZNjbleZ6/KKw6LmreHarVW9Rqd6jXPXZ2XQeeV0OjUWeWVOi6zt7WMPTozU0mgC4kEYlEAvP5HFxRKMBx7esSqNiyDLYkOtOS3ChPDR3Hhm1XWI5itAye/4RUKoVYLIbxeAzu5/cf+Pb4iMFgcKXf7/8TVDscDjEajdDr9bBcLvEOEjWyV70hhWoAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
&lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;SSH connection details&quot; title=&quot;SSH connection details&quot; src=&quot;/static/2e05b31bec3d7c2c5d15cf400327822a/c1b63/ci-enable-ssh.png&quot; srcSet=&quot;/static/2e05b31bec3d7c2c5d15cf400327822a/5a46d/ci-enable-ssh.png 300w,/static/2e05b31bec3d7c2c5d15cf400327822a/0a47e/ci-enable-ssh.png 600w,/static/2e05b31bec3d7c2c5d15cf400327822a/c1b63/ci-enable-ssh.png 1200w,/static/2e05b31bec3d7c2c5d15cf400327822a/d4e7f/ci-enable-ssh.png 1416w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
&lt;/a&gt;
&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Those details are likely be different in your case, but your primarily interest should fall onto the machine&amp;#x27;s host (&lt;code&gt;100.26.97.124&lt;/code&gt;) and port number (&lt;code&gt;64536&lt;/code&gt;). Remember those. CircleCI would also print out a shorthand connection command, which we are going to use in the next step.&lt;/p&gt;&lt;h2 id=&quot;into-the-remote&quot;&gt;Into the remote!&lt;/h2&gt;&lt;h3 id=&quot;using-default-key&quot;&gt;Using default key&lt;/h3&gt;&lt;p&gt;(Recommended) If your main SSH key is the one associated with your GitHub account, execute the SSH command CircleCI printed out in the &amp;quot;Enable SSH&amp;quot; step:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh -p 64536 100.26.97.124
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;using-explicit-key&quot;&gt;Using explicit key&lt;/h3&gt;&lt;p&gt;In case you have got multiple SSH keys you would have to specify which one to use to connect to the remote machine. Provide the path to the same SSH key you use for GitHub as the value of the &lt;code&gt;-i&lt;/code&gt; flag in &lt;code&gt;ssh&lt;/code&gt; command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh -i ~/.ssh/id_rsa -p 64536 100.26.97.124
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;establishing-connection&quot;&gt;Establishing connection&lt;/h3&gt;&lt;p&gt;Using a UNIX-based computer, you can connect to a remote SSH server by running &lt;code&gt;ssh&lt;/code&gt; command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh -p 64536 100.26.97.124
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Answer &amp;quot;yes&amp;quot; if prompted with &amp;quot;Are you sure you want to continue connecting&amp;quot;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;When successfully executed, you will notice your terminal&amp;#x27;s working directory changing to the remote machine&amp;#x27;s name. Any commands issued in this process from now on are executed on the remote machine. For example, we can run the entire build again, or each command in isolation (i.e. &lt;code&gt;npm test&lt;/code&gt;, or &lt;code&gt;npm run build&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1200px&quot;&gt;
&lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/34c1a5f6ff980431fa72c80b1905bead/f793b/terminal-remote.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
&lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:57.333333333333336%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABqklEQVQoz6WQXUvCUBjHNezufI0+R11Ed9adFFFQXmgJ0qVZ1KUV1EdQ6DOoQUrgVW5q5mrtRaxDRBGZus1tzrY9bctG9EbSH37n4Tyc/+/ieDxWDicmfftrY2hqZxItBhfQzMI0WomF0ezSLJqbm0eBQACtRiIokUigaDSKQqEQCofDKGLtloNB5Pf7USwW83neUzk6DvK1QrPMkI3z0jm+pGhcO6MwWSQxQRKYJElM0zSuVCq4XC5jgiAcSqUSpiiqwXFcq1qtLrvCk9PC5mO3DcwdD/VnDm5FDGzzCm6eruG52YZOpwO6roMoiqCqKsiy7Mxer+dgh2GYDVdYKBbjt00R6Dvcx0rDwMq10ZB540G5N9otwVBVxbA6P6HZQpZl113hBVuL28uuIeiC1jGlvghtrQXdvgSKrICmOR0wTcPC/Iidl4Ew7gp5jt94a4DunN/kk+h3oX0ZlF7M4fOtcH0g1Cz0Iel9+cN6vb4N/wzP81uuMJVKjedyuf1MJrObzWb3rPkn7LfpdHo3n88fJJPJcUcmCILXGjYjFqND4htMu+uVJMn7CmGhZkbvPxPEAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
&lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Remote machine opened in terminal&quot; title=&quot;Remote machine opened in terminal&quot; src=&quot;/static/34c1a5f6ff980431fa72c80b1905bead/c1b63/terminal-remote.png&quot; srcSet=&quot;/static/34c1a5f6ff980431fa72c80b1905bead/5a46d/terminal-remote.png 300w,/static/34c1a5f6ff980431fa72c80b1905bead/0a47e/terminal-remote.png 600w,/static/34c1a5f6ff980431fa72c80b1905bead/c1b63/terminal-remote.png 1200w,/static/34c1a5f6ff980431fa72c80b1905bead/f793b/terminal-remote.png 1404w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
&lt;/a&gt;
&lt;/span&gt;&lt;/p&gt;&lt;p&gt;By default you are going to see the &lt;code&gt;/home/circleci/&lt;/code&gt; directory opened. Depending on your CircleCI configuration you will have a different directory tree there. In my case I have configured my job to run in the &lt;code&gt;~/release&lt;/code&gt; folder:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;version: 2
jobs:
build:
working_directory: ~/release
steps:
- checkout
- ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pay attention to your setup and directory structure when applying commands used in this article. I am going to list those according to my setup to remain consistent.&lt;/p&gt;&lt;h2 id=&quot;taking-a-snapshot&quot;&gt;Taking a snapshot&lt;/h2&gt;&lt;p&gt;Working with a remote file-system is helpful, but you might quickly find yourself limited. It is a different environment that lacks your favorite IDE and other helpful tools you use for debugging. It may be helpful to know how to download directories and files from the remote machine.&lt;/p&gt;&lt;p&gt;Although you should strive towards your project being reproducible, I highly recommend to download a complete snapshot of the file-system, &lt;em&gt;including installed dependencies&lt;/em&gt;. This way you eliminate any possible deviations and operate on the 1-1 instance of your project from the failed job. Depending on the project&amp;#x27;s (and its dependencies) size, transferring its entire directory may take a significant amount of time. We can compress the working directory into a tarball to decrease its size and make the download procedure faster.&lt;/p&gt;&lt;p&gt;Being on the remote machine let&amp;#x27;s compress the current working directory into a tarball archive:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ tar -czvf snapshot.tar.gz ./release
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-c&lt;/code&gt; create an archive;&lt;/li&gt;&lt;li&gt;&lt;code&gt;-z&lt;/code&gt; compress the archive using gzip;&lt;/li&gt;&lt;li&gt;&lt;code&gt;-v&lt;/code&gt; display progress in the terminal (verbose);&lt;/li&gt;&lt;li&gt;&lt;code&gt;-f&lt;/code&gt; accept a file name of the archive.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;After the archive is created you can transfer &lt;code&gt;snapshot.tar.gz&lt;/code&gt; using SCP (&lt;a href=&quot;https://en.wikipedia.org/wiki/Secure_copy&quot;&gt;Secure Copy Protocol&lt;/a&gt;). Open a new terminal window, because &lt;strong&gt;you need to be on the local machine&lt;/strong&gt; to do this step.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ scp &amp;lt;OPTIONS&amp;gt; &amp;lt;USER&amp;gt;@&amp;lt;HOST&amp;gt;:/&amp;lt;SRC_PATH&amp;gt; &amp;lt;DEST_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ scp -P 64536 circleci@100.26.97.124:/home/circleci/snapshot.tar.gz ~/Desktop
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;-P&lt;/code&gt; connection port to use (default is &lt;code&gt;20&lt;/code&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This command will copy the file at &lt;code&gt;/home/circleci/snapshot.tar.gz&lt;/code&gt; from the remote machine to your local &lt;code&gt;~/Desktop&lt;/code&gt;. Unarchive the snapshot of the CI and debug it as if it was a regular folder, because it is now.&lt;/p&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;Thanks for reading through this article! I hope it will be of good use to you when debugging the next failed CI. Let me know your thoughts on the topic &lt;a href=&quot;https://twitter.com/kettanaito&quot;&gt;on Twitter&lt;/a&gt;!&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;links--materials&quot;&gt;Links &amp;amp; Materials&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://circleci.com/docs/2.0/ssh-access-jobs/&quot;&gt;Debugging with SSH&lt;/a&gt; (CircleCI documentation)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://jumpcloud.com/blog/what-are-ssh-keys/&quot;&gt;What are SSH keys?&lt;/a&gt; by JumpCloud&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://help.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh&quot;&gt;Connecting to GitHub with SSH&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Debounce vs Throttle: Definitive Visual Guide]]></title><description><![CDATA[Introduction When it comes to debounce and throttle developers often confuse the two. Choosing the right one is, however, crucial, as they…]]></description><link>https://redd.one/debounce-vs-throttle</link><guid isPermaLink="false">https://redd.one/debounce-vs-throttle</guid><pubDate>Mon, 23 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;&lt;p&gt;When it comes to debounce and throttle developers often confuse the two. Choosing the right one is, however, crucial, as they bear a different effect. If you are a visual learner as myself, you will find this interactive guide useful to differentiate between &lt;code&gt;throttle&lt;/code&gt; and &lt;code&gt;debounce&lt;/code&gt; and better understand when to use each.&lt;/p&gt;&lt;h2 id=&quot;first-things-first&quot;&gt;First things first&lt;/h2&gt;&lt;p&gt;Throttling and debouncing are two ways to optimize event handling. Before we begin, let&amp;#x27;s take a moment to briefly revise the basics of events. In this article I&amp;#x27;m going to use JavaScript in all examples, yet the conepts they illustrate are not bound to any specific language.&lt;/p&gt;&lt;p&gt;Event is an action that occurs in the system. In front-end development that system is usually a browser. For example, when you resize a browser window the &amp;quot;resize&amp;quot; event is fired, and when you click on a button the &amp;quot;click&amp;quot; event is. We are interested in events to attach our own logic to them. That logic is represented as a function that is called a handler function (because it handles the event). Such handler functions may handle a UI element update on resize, display a modal window upon a button click, or execute an arbitrary logic in response to any event.&lt;/p&gt;&lt;p&gt;In JavaScript you can react to events using event listeners. &lt;em&gt;Event listener&lt;/em&gt; is a function that listens to the given event on a DOM element and executes a handler function whenever that event occurs. To add an event listener to an element (target) you should use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener&quot;&gt;&lt;code&gt;addEventListener&lt;/code&gt;&lt;/a&gt; function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;target.addEventListener(eventName, handler, options)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;lets-throw-a-ball&quot;&gt;Let&amp;#x27;s throw a ball!&lt;/h2&gt;&lt;p&gt;Let&amp;#x27;s build a ball throwing machine. Our machine would have a button that, when pushed, throws a ball. To describe this cause-and-effect relation between the button click and a ball throw we can use &lt;code&gt;addEventListener&lt;/code&gt; on our button element:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const button = document.getElementById(&amp;#x27;button&amp;#x27;)
button.addEventListener(&amp;#x27;click&amp;#x27;, function () {
throwBall()
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This reads as: &lt;em&gt;whenever the button is clicked, execute the &lt;code&gt;throwBall()&lt;/code&gt; function&lt;/em&gt;. The details of &lt;code&gt;throwBall&lt;/code&gt; function are not important, as it represents &lt;em&gt;any logic&lt;/em&gt; bound to an event.&lt;/p&gt;&lt;p&gt;Hinges are tightened and the screws are steady—let&amp;#x27;s put our ingenious invention to the test!&lt;/p&gt;&lt;div class=&quot;sc-AxirZ iRFNfH&quot;&gt;&lt;div width=&quot;100%&quot; class=&quot;sc-AxjAm VendingMachine__VendingMachineContainer-sc-5h4te9-1 hcqTIA&quot;&gt;&lt;div class=&quot;VendingMachine__RawButtonContainer-sc-5h4te9-2 eYdQkF&quot;&gt;&lt;button aria-label=&quot;Throw a ball&quot; class=&quot;VendingMachine__RedButton-sc-5h4te9-4 lhbXti&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;img src=&quot;static/vendingMachine-9a0d0ccbb1d6ce4dc8911fbbb6275dca.png&quot; alt=&quot;Ball vending machine&quot; class=&quot;VendingMachine__StyledImage-sc-5h4te9-3 bljudg&quot;/&gt;&lt;canvas height=&quot;145&quot; width=&quot;115&quot; class=&quot;VendingMachine__RawStyledCanvas-sc-5h4te9-0 kcAVSi&quot;&gt;&lt;/canvas&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;small&gt;← Press the red button of the machine.&lt;/small&gt;&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Button clicked:&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0 time(s)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Event handler called:&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0 time(s)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Whenever we press the button we produce the &amp;quot;click&amp;quot; event, to which event listener reacts with the &lt;code&gt;throwBall()&lt;/code&gt; function. In other words, one button click results into one handler function call and one ball being thrown.&lt;/p&gt;&lt;div&gt;By default, event handler function executes with 1-1 ratio to the event call.&lt;/div&gt;&lt;p&gt;There are cases when such direct proportion may become undesired. For instance, what if throwing a ball was an expensive operation, or we couldn&amp;#x27;t afford to throw more than 1 ball in half a second? In those cases we would have to limit the amount of times our handler function is being called.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Throttling&lt;/strong&gt; and &lt;strong&gt;debouncing&lt;/strong&gt; are two most common ways to control a handler function response rate to an event. Let&amp;#x27;s analyze each of them more closely by tweaking our ball machine.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;throttle&quot;&gt;Throttle&lt;/h2&gt;&lt;p&gt;A throttled function is called once per &lt;em&gt;N&lt;/em&gt; amount of time. Any additional function calls within the specified time interval are ignored.&lt;/p&gt;&lt;h3 id=&quot;implementing-throttle&quot;&gt;Implementing throttle&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function throttle(func, duration) {
let shouldWait = false
return function (...args) {
if (!shouldWait) {
func.apply(this, args)
shouldWait = true
setTimeout(function () {
shouldWait = false
}, duration)
}
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Depending on the use case such simplified implementation may not be enough. I recommend looking into &lt;a href=&quot;https://www.npmjs.com/package/lodash.throttle&quot;&gt;&lt;code&gt;lodash.throttle&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://underscorejs.org/#throttle&quot;&gt;&lt;code&gt;_.throttle&lt;/code&gt;&lt;/a&gt; packages then.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;The &lt;code&gt;throttle&lt;/code&gt; function accepts two arguments: &lt;code&gt;func&lt;/code&gt;, which is a function to throttle, and &lt;code&gt;duration&lt;/code&gt;, which is the duration (in ms) of the throttling interval. It returns a &lt;em&gt;throttled function&lt;/em&gt;. There are implementations that also accept the &lt;code&gt;leading&lt;/code&gt; and &lt;code&gt;trailing&lt;/code&gt; parameters that control the first (leading) and the last (trailing) function calls, but I&amp;#x27;m going to skip those to keep the example simple.&lt;/p&gt;&lt;p&gt;To throttle our machine&amp;#x27;s button click we need to pass the event handler function as the first argument to &lt;code&gt;throttle&lt;/code&gt;, and specify a throttling interval as the second argument:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=3,5&quot; focusedLines=&quot;3,5&quot;&gt;button.addEventListener(
&amp;#x27;click&amp;#x27;,
throttle(function () {
throwBall()
}, 500)
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;#x27;s how our patched ball machine would work with the throttling applied:&lt;/p&gt;&lt;div class=&quot;sc-AxirZ iRFNfH&quot;&gt;&lt;div width=&quot;100%&quot; class=&quot;sc-AxjAm VendingMachine__VendingMachineContainer-sc-5h4te9-1 hcqTIA&quot;&gt;&lt;div class=&quot;VendingMachine__RawButtonContainer-sc-5h4te9-2 eYdQkF&quot;&gt;&lt;button aria-label=&quot;Throw a ball&quot; class=&quot;VendingMachine__RedButton-sc-5h4te9-4 lhbXti&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;img src=&quot;static/vendingMachine-9a0d0ccbb1d6ce4dc8911fbbb6275dca.png&quot; alt=&quot;Ball vending machine&quot; class=&quot;VendingMachine__StyledImage-sc-5h4te9-3 bljudg&quot;/&gt;&lt;canvas height=&quot;145&quot; width=&quot;115&quot; class=&quot;VendingMachine__RawStyledCanvas-sc-5h4te9-0 kcAVSi&quot;&gt;&lt;/canvas&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;small&gt;← Press the red button of the machine.&lt;/small&gt;&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Button clicked:&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0 time(s)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Event handler called:&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0 time(s)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;fieldset&gt;&lt;legend&gt;Throttle options&lt;/legend&gt;&lt;label for=&quot;throttleDuration&quot;&gt;Duration: 500ms&lt;/label&gt;&lt;input type=&quot;range&quot; id=&quot;throttleDuration&quot; min=&quot;0&quot; max=&quot;2000&quot; step=&quot;100&quot; value=&quot;500&quot;/&gt;&lt;/fieldset&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;No matter how often we press the button a ball won&amp;#x27;t be thrown more than once per throttled interval (500ms in our case). That&amp;#x27;s a great way to keep our ball machine from overheating during the busy hours!&lt;/p&gt;&lt;div&gt;Throttle is a spring that throws balls: after a ball flies out it needs some time to shrink back, so it cannot throw any more balls unless it&amp;#x27;s ready.&lt;/div&gt;&lt;h3 id=&quot;when-to-use-throttle&quot;&gt;When to use throttle?&lt;/h3&gt;&lt;p&gt;Use &lt;code&gt;throttle&lt;/code&gt; to &lt;em&gt;consistently&lt;/em&gt; react to a frequent event.&lt;/p&gt;&lt;p&gt;This technique ensures consistent function execution within a given time interval. Since throttle is bound to a timeframe, a dispatched event handler should be ready to accept an intermediate state of event.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Common use cases for a throttled function:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Any consistent UI update after window &lt;code&gt;resize&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Performance-heavy operations on the server or client&lt;/li&gt;&lt;/ul&gt;&lt;hr/&gt;&lt;h2 id=&quot;debounce&quot;&gt;Debounce&lt;/h2&gt;&lt;p&gt;A debounced function is called after &lt;em&gt;N&lt;/em&gt; amount of time passes since its last call. It reacts to a seemingly resolved state and implies a delay between the event and the handler function call.&lt;/p&gt;&lt;h3 id=&quot;implementing-debounce&quot;&gt;Implementing debounce&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function debounce(func, duration) {
let timeout
return function (...args) {
const effect = () =&amp;gt; {
timeout = null
return func.apply(this, args)
}
clearTimeout(timeout)
timeout = setTimeout(effect, duration)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;For more complicated scenarios consider &lt;a href=&quot;https://www.npmjs.com/package/lodash.debounce&quot;&gt;&lt;code&gt;lodash.debounce&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://underscorejs.org/#debounce&quot;&gt;&lt;code&gt;_.debounce&lt;/code&gt; &lt;/a&gt; packages then.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;The &lt;code&gt;debounce&lt;/code&gt; function accepts two arguments: &lt;code&gt;func&lt;/code&gt;, which is a function to debounce, and &lt;code&gt;duration&lt;/code&gt;, which is the amount of time (in ms) to pass from the last function call. It returns a &lt;em&gt;debounced function&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;To apply debouncing to our example we would have to wrap the button click handler in the &lt;code&gt;debounce&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=3,5&quot; focusedLines=&quot;3,5&quot;&gt;button.addEventListener(
&amp;#x27;click&amp;#x27;,
debounce(function () {
throwBall()
}, 500)
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While the call signature of &lt;code&gt;debounce&lt;/code&gt; is often similar to the one in &lt;code&gt;throttle&lt;/code&gt;, it produces a much different effect when applied. Let&amp;#x27;s see how our machine will behave if its button clicks are debounced:&lt;/p&gt;&lt;div class=&quot;sc-AxirZ iRFNfH&quot;&gt;&lt;div width=&quot;100%&quot; class=&quot;sc-AxjAm VendingMachine__VendingMachineContainer-sc-5h4te9-1 hcqTIA&quot;&gt;&lt;div class=&quot;VendingMachine__RawButtonContainer-sc-5h4te9-2 eYdQkF&quot;&gt;&lt;button aria-label=&quot;Throw a ball&quot; class=&quot;VendingMachine__RedButton-sc-5h4te9-4 lhbXti&quot;&gt;&lt;/button&gt;&lt;/div&gt;&lt;img src=&quot;static/vendingMachine-9a0d0ccbb1d6ce4dc8911fbbb6275dca.png&quot; alt=&quot;Ball vending machine&quot; class=&quot;VendingMachine__StyledImage-sc-5h4te9-3 bljudg&quot;/&gt;&lt;canvas height=&quot;145&quot; width=&quot;115&quot; class=&quot;VendingMachine__RawStyledCanvas-sc-5h4te9-0 kcAVSi&quot;&gt;&lt;/canvas&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;small&gt;← Press the red button of the machine.&lt;/small&gt;&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Button clicked:&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0 time(s)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Event handler called:&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0 time(s)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;fieldset&gt;&lt;legend&gt;Debounce options&lt;/legend&gt;&lt;label for=&quot;debounceDuration&quot;&gt;Duration: 500ms&lt;/label&gt;&lt;input type=&quot;range&quot; id=&quot;debounceDuration&quot; min=&quot;0&quot; max=&quot;2000&quot; step=&quot;100&quot; value=&quot;500&quot;/&gt;&lt;/fieldset&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;If we keep pressing the button fast enough no balls will be thrown at all, unless a debounce duration (500ms) passes since the last click. It is if our machine treats any amount of button clicks within a defined time period as &lt;em&gt;a single event&lt;/em&gt; and handles it respectively.&lt;/p&gt;&lt;div&gt;Debounce is an overloaded waiter: if you keep asking him your requests will be ignored until you stop and give him some time to think about your latest inquiry.&lt;/div&gt;&lt;h3 id=&quot;when-to-use-debounce&quot;&gt;When to use debounce?&lt;/h3&gt;&lt;p&gt;Use debounce to &lt;em&gt;eventually&lt;/em&gt; react to a frequent event.&lt;/p&gt;&lt;p&gt;Debounce is useful when you don&amp;#x27;t need an intermediate state and wish to respond to the end state of the event. That being said, you need to take into account an inevitable delay between the event and the response to it when using &lt;code&gt;debounce&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Common use cases for a debounced function:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Asynchronous search suggestions&lt;/li&gt;&lt;li&gt;Updates batching on the server&lt;/li&gt;&lt;/ul&gt;&lt;hr/&gt;&lt;h2 id=&quot;common-problems&quot;&gt;Common problems&lt;/h2&gt;&lt;h3 id=&quot;re-declaring-debouncedthrottled-function&quot;&gt;Re-declaring debounced/throttled function&lt;/h3&gt;&lt;p&gt;One of the most common mistakes when working with these rate limiting functions is &lt;em&gt;repeatedly re-declaring them&lt;/em&gt;. You see, both &lt;code&gt;debounce&lt;/code&gt; and &lt;code&gt;throttle&lt;/code&gt; work due to &lt;em&gt;the same (debounced/throttled) function reference&lt;/em&gt; being called. It is absolutely necessary to ensure you declare your debounced/throttled function only once.&lt;/p&gt;&lt;p&gt;Allow me to illustrate this pitfall. Take a look at this click event handler:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;button.addEventListener(&amp;#x27;click&amp;#x27;, function handleButtonClick() {
return debounce(throwBall, 500)
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It may look fine at first, but in fact nothing is going to be debounced. That is because the &lt;code&gt;handleButtonClick&lt;/code&gt; function is not debounced, but instead we debounce the &lt;code&gt;throwBall&lt;/code&gt; function.&lt;/p&gt;&lt;p&gt;Instead, we should debounce an entire &lt;code&gt;handleButtonClick&lt;/code&gt; function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; metastring=&quot;focusedLines=3,5&quot; focusedLines=&quot;3,5&quot;&gt;button.addEventListener(
&amp;#x27;click&amp;#x27;,
debounce(function handleButtonClick() {
return throwBall()
}, 500)
)
&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;Remeber that the event handler function must be debounced/throttled only once. The returned function must be provided to any event listeners.&lt;/div&gt;&lt;h4 id=&quot;react-example&quot;&gt;React example&lt;/h4&gt;&lt;p&gt;If you are familiar with &lt;a href=&quot;https://reactjs.org&quot;&gt;React&lt;/a&gt; you may also recognize the following declaration as being invalid:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot; metastring=&quot;focusedLines=8&quot; focusedLines=&quot;8&quot;&gt;class MyComponent extends React.Component {
handleButtonClick = () =&amp;gt; {
console.log(&amp;#x27;The button was clicked&amp;#x27;)
}
render() {
return (
&amp;lt;button onClick={debounce(this.handleButtonClick, 500)}&amp;gt;
Click the button
&amp;lt;/button&amp;gt;
)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since &lt;code&gt;debounce&lt;/code&gt; is called during the render, each MyComponent&amp;#x27;s re-render will produce a new instance of a debounced &lt;code&gt;handleButtonClick&lt;/code&gt; function, resulting into no effect being applied.&lt;/p&gt;&lt;p&gt;Instead, the &lt;code&gt;handleButtonClick&lt;/code&gt; &lt;em&gt;declaration&lt;/em&gt; should be debounced:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot; metastring=&quot;focusedLines=2,7&quot; focusedLines=&quot;2,7&quot;&gt;class MyComponent extends React.Component {
handleButtonClick = debounce(() =&amp;gt; {
console.log(&amp;#x27;The button was clickeds&amp;#x27;)
}, 500)
render() {
return &amp;lt;button onClick={this.handleButtonClick}&amp;gt;Click the button&amp;lt;/button&amp;gt;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;finding-optimal-duration&quot;&gt;Finding optimal duration&lt;/h3&gt;&lt;p&gt;With both &lt;code&gt;debounce&lt;/code&gt; and &lt;code&gt;throttle&lt;/code&gt; finding a duration time optimal for UX &lt;em&gt;and&lt;/em&gt; performance is important. Choosing a fast interval will not have impact on performance, and picking a too long interval will make your UI feel sluggish.&lt;/p&gt;&lt;p&gt;The truth is, there is no magical number to this, as time interval will differ for each use case. The best advice I can give you is to not copy any intervals blindly, but test what works the best for your application/users/server. You may want to conduct A/B testing to find that.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;Thank you for reading through this guide! Of course, there&amp;#x27;s much more to events handling, and throttling and debouncing are not the only techniques you may use in practice. Let me know if you liked this article by reposting or retweeting it.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Special thanks to &lt;a href=&quot;https://codepen.io/AlexRA96&quot;&gt;Alexander Fernandes&lt;/a&gt; for &amp;quot;Ball Bouncing Physics&amp;quot; project used for balls physics in the vending machine example.&lt;/p&gt;&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[Thinking in Functions, Part I: The Input/Output pattern]]></title><description><![CDATA[Introduction I was lucky to work with some great minds in the industry. Once I’ve befriended a brilliant Java developer and we spent a…]]></description><link>https://redd.one/thinking-in-functions</link><guid isPermaLink="false">https://redd.one/thinking-in-functions</guid><pubDate>Mon, 09 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;&lt;p&gt;I was lucky to work with some great minds in the industry. Once I’ve befriended a brilliant Java developer and we spent a decent amount of lunches chatting over patterns and approaches in programming. At that time I was working on a schema design for the forms validation in my project, which involved a lot of functional heavy-lifting. Each time we discussed a particular problem I was fascinated by the way he approached functional design. I’ve adopted one of his thinking patterns during the process, and it has changed the way I comprehend functions since then.&lt;/p&gt;&lt;p&gt;If you are coming from a functional programming background the things I’m about to say may bear no novelty for you. However, I know there are developers who are not accustomed to strong types and Hindley-Milner’s notation isn’t exactly the first thing that pops up in their heads when they think of a function. I address this article to those people.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;the-inputoutput-pattern&quot;&gt;The Input/Output pattern&lt;/h2&gt;&lt;p&gt;We know that a function accepts arguments and returns a value. What we sometimes overlook is that this characteristic is an incredibly powerful design pattern we can use to write better functions.&lt;/p&gt;&lt;p&gt;Try to write any function by answering these two questions first:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;What is this function’s input (what arguments it accepts)?&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;What is this function’s output (what data it returns)?&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Doing so allows you to put technical details aside and focus on a function as an input/output operation, which, in fact, it is. The answers you give may hint certain implementational details, but, most importantly, they define clear constrains over a function’s responsibilities long before any actual code is being written.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;You may use abstract types for your answers. For example, a function may accept a list of apples and return a happy fox. Such type abstractions detach the call signature from the implementation even further.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Let’s put this into practice. Say, you need to write a function that validates a form field. There are myriad of things that can affect a field’s validation, but you can leave those aside and answer the Input/Output questions first:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;My function accepts a &lt;em&gt;field&lt;/em&gt;;&lt;/li&gt;&lt;li&gt;My function returns a &lt;em&gt;validation verdict&lt;/em&gt; (&lt;code&gt;boolean&lt;/code&gt;).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;When written down, these answers represent the function’s call signature:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function validate(field: Field): boolean
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You may have no idea what the &lt;code&gt;Field&lt;/code&gt; type may be at this point, but you know what it represents. The same can be said about the &lt;code&gt;validate&lt;/code&gt; function in total: no matter which factors affect a field’s validity, you have to resolve to the boolean verdict in the end. Defined input and output act as a restriction that prevents our function from becoming too smart along the deadline-driven development. That ensures that the logic we write lies within the function’s responsibility and remains simple, satisfying both single responsibility and KISS principles.&lt;/p&gt;&lt;p&gt;Adopting this pattern doesn’t mean you should immediately rewrite your code in &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt; or any other strongly-typed language. First of all, it is the way of thinking about your functions. It’s fine to keep the input and output noted in a JSDoc block or in your sketchbook. Start from changing the way you think, and the tools will follow.&lt;/p&gt;&lt;div&gt;Similar to how you put the user’s needs first before making proper UX decisions, you think about what data your function accepts and returns in order to establish the boundaries of its future implementation.&lt;/div&gt;&lt;h3 id=&quot;pattern-at-scale&quot;&gt;Pattern at scale&lt;/h3&gt;&lt;p&gt;Thinking of functions using this pattern is great, but what about real-world operations that often consist of multiple functions and represent more complex logic?&lt;/p&gt;&lt;p&gt;The truth is, even the most complex function can be written as a set of consecutively executing smaller functions. When you approach the task this way you can focus on designing each individual function at a time. However, keeping functions in isolation is dangerous, as you may end up with multiple puzzle pieces that don’t fit together. There is a rule of functional composition to avoid that problem:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Two functions are composable if the output of one can serve as the input to another.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Knowing that, let’s implement a fairly complex operation that accepts a user and returns the amount of likes under all of their posts. To prevent the complexity, we can describe this operation as a sequence of steps:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Get a user → get user’s posts → get the amount of post’s likes&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Each of these steps is a function, and we can apply the Input/Output pattern to design its call signature, keeping in mind the composition principle.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const getUser = (id: string) =&amp;gt; User
const getPosts = (user: User) =&amp;gt; Post[]
const getTotalLikes = (posts: Post[]) =&amp;gt; number
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Such high-level overview of a functional chain allows you to follow the data flow without distractions and highlight potential problems in the logic. Moreover, it is just a fun exercise to do.&lt;/p&gt;&lt;div&gt;In the end, functions are about transforming the data, so use all means available to ensure that transformation is coherent and efficient.&lt;/div&gt;&lt;h3 id=&quot;one-more-thing&quot;&gt;One more thing!&lt;/h3&gt;&lt;p&gt;There is one more hidden gem about the Input/Output approach. Let’s say you answer those primary questions with &amp;quot;&lt;em&gt;my function accepts a list of strings and returns a number&lt;/em&gt;&amp;quot;. Congratulations, you have just written a unit test for your function!&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;expect(myFunc([&amp;#x27;a&amp;#x27;, &amp;#x27;b&amp;#x27;])).toEqual(2)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The result of this pattern may be reflected in a test scenario, backing up function design decisions with an actual unit test. This encourages TDD (test-driven development) and BDD (behavior-driven development), since we express our function’s intent by describing its input and output.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;&lt;p&gt;Focusing on a function’s input and output types defines a concise specification of that function:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A function’s call signature;&lt;/li&gt;&lt;li&gt;A minimal unit test for that function.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Implementing things according to the specification is a pleasant and safe experience I absolutely recommend you to get accustomed to.&lt;/p&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;I hope this pattern helps you next time you decide to write a function. Let me know if you would like to know more practical patterns and approaches to functional design by liking this post and letting me know in the comments.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Art of Code Review: From hatred to acceptance]]></title><description><![CDATA[Introduction Hi! My name is Artem, and I am a software engineer who spent the last 2,5 years developing a multi-country e-commerce solution…]]></description><link>https://redd.one/the-art-of-code-review</link><guid isPermaLink="false">https://redd.one/the-art-of-code-review</guid><pubDate>Thu, 27 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;&lt;p&gt;Hi! My name is Artem, and I am a software engineer who spent the last 2,5 years developing a multi-country e-commerce solution with millions of euro of annual revenue. During that journey I had a chance to work with more than 15 front-end engineers of different seniority level, teaching them what I knew and learning from them what I didn’t. Today I would like to share my experience being a code reviewer, and give you some pieces of advice to become a good one.&lt;/p&gt;&lt;p&gt;I have been known for my notorious code reviews within the team, which some people hated, and others hated even more. In the end, it’s a joy to see the people who despised my reviews coming back and thanking me for a great time we both spent improving. Undoubtedly, my reviewing skills have a long way to go, but the positive feedback I’ve received over time motivated me to write this article. I would like to explore what made people love and hate my reviews, and how you can learn from that to become a better reviewer in your team.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;code-review-is-a-part-of-development&quot;&gt;Code review is a part of development&lt;/h2&gt;&lt;p&gt;I will start from assuring you that a code review by no means bears a lesser priority in your work than actual coding. I would argue that it’s even more important, since it’s one of the most useful feedback loops you can get for free. A flaw in the logic discovered at the review phase can save unnecessary stress for you and wasted money for your customer.&lt;/p&gt;&lt;img src=&quot;static/man-computer.25d56e7a.svg&quot; alt=&quot;Image of a man behind computers&quot;/&gt;&lt;p&gt;Understanding an importance of a code review as a part of your job is great, but make sure the other team members are on the same page with you. That doesn’t concern developers only, but project managers, scrum masters, product owners, and, most importantly, clients.&lt;/p&gt;&lt;div&gt;Code review is not a voluntary activity, but a righteous duty that must be accounted, respected, and paid.&lt;/div&gt;&lt;p&gt;Don’t be afraid to dive deeper into the code you’re reviewing, open a source code and browse through the related files to understand the nature of the changes better. With more pull requests behind your back, you will be able to perform that much faster, thus, delivering a profound in-depth feature analyzis. On the contrary, neglecting things in a review may backfire into much more work that it would’ve taken to get your head around the changes.&lt;/p&gt;&lt;p&gt;Here are a few arguments I find useful when explaining how code review contributes to a product:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It ensures quality of the code, thus, quality of a product;&lt;/li&gt;&lt;li&gt;It reduces the amount of technical debt in the future, making the maintenance cost lower;&lt;/li&gt;&lt;li&gt;It provides knowledge sharing between developers, resulting into more effective work, and eliminating the bus factor.&lt;/li&gt;&lt;/ul&gt;&lt;hr/&gt;&lt;h2 id=&quot;polite-reviewer-sensible-issuer&quot;&gt;Polite reviewer, sensible issuer&lt;/h2&gt;&lt;p&gt;Be polite in your code reviews. We are all humans doing our job and we strive to do it the best we can. While this sounds obvious, you may find a lot about your colleague if you ask them for a code review at 5PM on Friday before their long-awaited vacation.&lt;/p&gt;&lt;img src=&quot;static/pair-programming.d540cd78.svg&quot; alt=&quot;Image of a woman and a man pair programming&quot;/&gt;&lt;p&gt;One of the hardest parts of giving a code review is when your suggestions are taken as a personal offense. It’s understandable that nobody likes to spend a week on a feature and hear that they need to redo everything. That hurts for both the issuer and the reviewer. &lt;strong&gt;Let’s not do that&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;In my practice such situations happen for two reasons, which I&amp;#x27;m going to describe below.&lt;/p&gt;&lt;h3 id=&quot;reason-1-bad-start&quot;&gt;Reason #1: Bad start&lt;/h3&gt;&lt;p&gt;This often happens to people who rush into development without properly thinking the changes through. I am strongly convinced that a day of thinking saves a week of work.&lt;/p&gt;&lt;div&gt;Don’t be afraid to ask and discuss! It either confirms your approach, or saves a week of work in a wrong direction.&lt;/div&gt;&lt;p&gt;While this is feasible in a team, those who work alone may struggle to get their ideas evaluated. Bear in mind that there are plenty of people who can help you. You are not alone.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Here are the links to some of my favorite engineering communities:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://discord.gg/yNcseDS&quot;&gt;Reactiflux&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://discord.gg/y5ksVAS&quot;&gt;Nodeiflux&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;reason-2-the-way-you-say-it&quot;&gt;Reason #2: The way you say it&lt;/h3&gt;&lt;p&gt;There is a fine line between “it&amp;#x27;s aweful, redo it” and a meaningful explanation of why there is a better way to do it. Remember that a reviewer’s role is to act as a fresh pair of eyes. Trying to understand the changes, and showing compassion when something needs to be reworked, are the keys to maintaining healthy working relationship between the issuer and the reviewer.&lt;/p&gt;&lt;div&gt;Code review is a conversation, not a queue of commands.&lt;/div&gt;&lt;p&gt;It’s a good gesture to include technical explanations and links to useful articles or resources whenever requesting a change. Because a change request shouldn’t be taken as your sole desire, but as a necessary measure that benefits everybody. Had the time been critical, it’s useful to give your colleague a hand or, perhaps, pair program to achieve the common goal, which is a quality code being merged. Turning an unpleasing fact of redoing your work into a supportive opportunity to learn is a great twist of events!&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;review-logic-not-semicolons&quot;&gt;Review logic, not semicolons&lt;/h2&gt;&lt;p&gt;There was a time I had been evaluating missing commas and semicolons during my reviews. What a shame. This is redundant and contributes to absolutely nothing, but it’s a good example of how a technical debt is paid by wasting everybody’s time. We didn’t have any code auto-formatting, and people often ignored ESLint warnings in the console. Eh, those dark times.&lt;/p&gt;&lt;img src=&quot;static/typewriter.70eeeff5.svg&quot; alt=&quot;Image of two women near a big typewriter&quot;/&gt;&lt;div&gt;Automation reduces the amount of needless checks, and let’s you focus on the logic behind the changes, rather than syntax errors and typos.&lt;/div&gt;&lt;p&gt;Using tools like &lt;a href=&quot;https://prettier.io/&quot;&gt;Prettier&lt;/a&gt; and integrating linter checks into your CI can filter out typos and syntax issues as the first automated part of any pull request. That also helps to keep a codebase unified without putting to much thought into it. Time saved by automating the routine can be well spent on verifying the actual logic behind the changes.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;dealing-with-big-changes&quot;&gt;Dealing with big changes&lt;/h2&gt;&lt;p&gt;Developing a big product may lead into big pull request. The more massive the scope of a change becomes, the harder it is to evaluate its affect. Nobody wants to spend an entire day reviewing a single pull request, having a bunch of other tasks to finish.&lt;/p&gt;&lt;img src=&quot;static/version-control.ba7ddc84.svg&quot; alt=&quot;Image of a man behind a computer, and a Git commit tree&quot;/&gt;&lt;p&gt;As a rule of thumb let&amp;#x27;s try to prevent big pull requests from happening as such.&lt;/p&gt;&lt;div&gt;Plan your work in small, deliverable, meaningful pieces. Iterate and learn from your planning.&lt;/div&gt;&lt;p&gt;However, the scope of a feature isn’t the only factor that affects a pull request’s size. There is also a technical depth, which may render you changing hundreds of files simply to adjust the hue of that one tiny icon.&lt;/p&gt;&lt;div&gt;Have a clear picture of your changes prior to making them. Think it through and discuss with your colleagues whenever in doubt.&lt;/div&gt;&lt;p&gt;What if a feature is small and has a clear development plan in mind, yet still requires to change the half of the entire codebase? How to handle that?&lt;/p&gt;&lt;p&gt;I can recommend an approach we’ve used for such pull requests in our team that we called &lt;strong&gt;intermediate code reviews&lt;/strong&gt;. It aims to resolve the amount of changes a reviewer should evaluate, thus resulting into short iterative review sessions. As an issuer, that means a rule to create a pull request, no matter the changes status, not later than the next morning after the first commit. For a reviewer it acts as a rule to always resolve assigned pull requests not later than the next morning. Of course, any necessary communication could also take place so that things are reviewed sooner, or later.&lt;/p&gt;&lt;p&gt;To eradicate huge diffs in your version control system try to issue pull requests from a child feature branch into a single parent feature branch. That way each review iteration ends with the changes being merged into the parent branch. Once the work is done, you can often merge parent branch into your master branch without any preceding code review.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;Please take all my suggestions with the grain of salt. I understand that a single approach may work differently for different teams, and that’s perfectly fine. It never hurts to know how others do — at worst you can learn from their mistakes.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Advanced forms in React made easy]]></title><description><![CDATA[Foreword Form is one of the most important interaction elements on the website. It’s easy to create a simple form. It’s hard to create a…]]></description><link>https://redd.one/advanced-forms-in-react-made-easy</link><guid isPermaLink="false">https://redd.one/advanced-forms-in-react-made-easy</guid><pubDate>Mon, 14 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;foreword&quot;&gt;Foreword&lt;/h2&gt;&lt;p&gt;Form is one of the most important interaction elements on the website. It’s easy to create a simple form. It’s hard to create a real-world form. For more than two years my team and I have been working on a huge e-commerce solution, and for more than two years we have struggled to fit the requirements into the form library we have chosen at the very beginning.&lt;/p&gt;&lt;p&gt;Refactoring was out of question. To our deepest surprise each of the modern solutions felt like walking into the same water. Instead, we have decided to create the one that would suit our needs foremost, eliminating present issues and focusing on missing functionality.&lt;/p&gt;&lt;p&gt;Today I am glad to share with you the outcome and demonstrate how it helped us to make the implementation cleaner and more maintainable.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;the-problems&quot;&gt;The problems&lt;/h2&gt;&lt;p&gt;Each of the points below can be dealt with to a certain extent. My emphasis here is that those can rather be handled on a form’s level, dramatically improving developer’s experience.&lt;/p&gt;&lt;h3 id=&quot;boilerplate&quot;&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;Our team hates writing boilerplate, so we couldn’t tolerate with providing blocks of configuration to high-order components around each form, or obscurely defining which fields a form will have before it renders.&lt;/p&gt;&lt;p&gt;Of course, you can abstract. Especially when you wish to end up in the hell of non-maintainable abstractions. The bottom line is, if a third-party solution results into you creating abstractions over it, then, probably, it never solved your problems in the first place.&lt;/p&gt;&lt;h3 id=&quot;obscure-declaration&quot;&gt;Obscure declaration&lt;/h3&gt;&lt;p&gt;Back in the day we needed to create an array of strings, which would represent the fields, and pass it to the high-order component before the form is even mounted. Today I see fields declared as Objects, or even worse, proving that this point is as valid as never.&lt;/p&gt;&lt;p&gt;Declaration of a form and its fields must be simple. I cannot stress more on how devastating an obscure declaration is to the code you write.&lt;/p&gt;&lt;h3 id=&quot;responsibility-delegation&quot;&gt;Responsibility delegation&lt;/h3&gt;&lt;p&gt;Form libraries tend to ask a developer to manage so many things that the one forgets about using a library at all. Maintaining and updating fields’ state, writing repetitive “validate” functions, or manually handling submit statuses — don’t be fooled to believe managing all this is your responsibility.&lt;/p&gt;&lt;p&gt;I value a solution being dynamic and flexible, but there is a fine line between being in control whenever you need to, and being forced to manage things when you shouldn’t.&lt;/p&gt;&lt;hr/&gt;&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;&lt;p&gt;That being said, it’s time to offer some solutions to those problems.&lt;/p&gt;&lt;p&gt;You and me are going to have a pair programming session right now, implementing a real-world registration form in our application. This is not going to be a short session, but, hopefully, a noteworthy one.&lt;/p&gt;&lt;h2 id=&quot;declaration&quot;&gt;Declaration&lt;/h2&gt;&lt;p&gt;A lot of solutions overcomplicate even at this starting point, but we are going to keep things simple. Clarity on the declaration level is a must and is an unrivaled privilege when working with a big code base.&lt;/p&gt;&lt;p&gt;Declaring the form’s layout must be simple:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/app/RegistrationForm.jsx
import React from &amp;#x27;react&amp;#x27;
import { Form } from &amp;#x27;react-advanced-form&amp;#x27;
import { Input } from &amp;#x27;...&amp;#x27;
export default class RegistrationForm extends React.Component {
render() {
return (
&amp;lt;Form&amp;gt;
&amp;lt;Input name=&amp;quot;userEmail&amp;quot; type=&amp;quot;email&amp;quot; required /&amp;gt;
&amp;lt;Input name=&amp;quot;userPassword&amp;quot; type=&amp;quot;password&amp;quot; required /&amp;gt;
&amp;lt;Input name=&amp;quot;confirmPassword&amp;quot; type=&amp;quot;password&amp;quot; required /&amp;gt;
&amp;lt;Input name=&amp;quot;firstName&amp;quot; /&amp;gt;
&amp;lt;Input name=&amp;quot;lastName&amp;quot; /&amp;gt;
&amp;lt;button&amp;gt;Register&amp;lt;/button&amp;gt;
&amp;lt;/Form&amp;gt;
)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No high-order components, no configurations. Nothing to subtract to make it even more simple.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Throughout this article I am going to refer to the pre-defined Input component. Creating a set of composite fields is an essential step when building an application, however, I will skip it for the sake of the article’s length.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;third-party-integration&quot;&gt;Third-party integration&lt;/h2&gt;&lt;p&gt;We often use great third-party fields libraries in our applications. React Advanced Form makes it easy to integrate any third-party library to work together. Take a look at the integration examples of one of the popular libraries:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;react-select&lt;/li&gt;&lt;li&gt;react-slider&lt;/li&gt;&lt;li&gt;react-datepicker&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;validation&quot;&gt;Validation&lt;/h2&gt;&lt;p&gt;Exposing a single validate function is as good as leaving you to implement repetitive logic over and over, covering something that form refused to cover.&lt;/p&gt;&lt;p&gt;We are going to take a different approach. The form is going to provide a versatile validation algorithm built-in, allowing developers to focus on actually writing validation rules, instead of repeating themselves in those “validate” functions.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;There is a defined logic applicable to any validation of any form:&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Any required field must have a value. In case it has any extra rules defined, those must be satisfied as well.&lt;/li&gt;&lt;li&gt;Any field must be validated in case it has some value and has applicable validation rules, regardless of whether it’s required or optional. Whenever the rules are provided, they must be satisfied.&lt;/li&gt;&lt;li&gt;Any asynchronous validation must execute only if the synchronous validation passed. Would you ever wanted to asynchronously validate a value of a wrong format?&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I am convinced that any sane implementation already has this or similar logic achieved on top of the form. But our goal is to make this a part of the form, preventing the repetition and controlling the necessity of the validation. Finally, we can say goodbye to the meaningless conditions like this one:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if (fields.email &amp;amp;&amp;amp; !!field.email.value) {
validateEmail(email)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;validation-schema&quot;&gt;Validation schema&lt;/h2&gt;&lt;p&gt;We have ditched a “validate” function, so how are we going to perform the validation then? After years of work on a multi-country platform, where each country demands specific validation rules, we have found that the most efficient way of writing and maintaining them is a &lt;em&gt;validation schema&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Validation schema is a plain Object of a defined structure, which allows to select fields and predicate their validity. It does not control when to apply the validation, but which validation to apply. The relevance of the validation (that would be applying the rules for present fields only, and the unified logic we have discussed above) is ensured by the form solution.&lt;/p&gt;&lt;h3 id=&quot;benefits-of-the-validation-schema&quot;&gt;Benefits of the validation schema&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Simple&lt;/strong&gt;. Objects are easy to read, maintain and compose dynamically.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Centralized&lt;/strong&gt;. Validation schema is meant to be defined on the root level of the app and serve as a global validation manifesto. Validation rules is something you want to apply application-wide in most of the cases (form-specific schemas are supported as well).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Decoupled&lt;/strong&gt;. Validation schema is decoupled from the validation messages to have a clear separation of concerns. Mixing validation rules and messages is the same as mixing business and view logic in your app.
Pure. Each rule is a pure function that can access the field’s value, fieldProps, and form — everything to craft the most complex validation logic without leaving the schema.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;schema-declaration&quot;&gt;Schema declaration&lt;/h3&gt;&lt;p&gt;Continuing on our Registration form, let’s define some clear validation requirements, and put them into a list:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;[type=&amp;quot;email&amp;quot;]&lt;/code&gt; fields must have a correct e-mail format,&lt;/li&gt;&lt;li&gt;&lt;code&gt;[type=&amp;quot;password&amp;quot;]&lt;/code&gt; fields must contain at least one capital letter,&lt;/li&gt;&lt;li&gt;&lt;code&gt;[type=&amp;quot;password&amp;quot;]&lt;/code&gt; fields must contain at least one number,&lt;/li&gt;&lt;li&gt;&lt;code&gt;[type=&amp;quot;password&amp;quot;]&lt;/code&gt; fields must be at least 6 characters long,&lt;/li&gt;&lt;li&gt;&lt;code&gt;[name=&amp;quot;confirmPassword&amp;quot;]&lt;/code&gt; value must equal to &lt;code&gt;[name=&amp;quot;userPassword&amp;quot;]&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;Note how userPassword and confirmPassword comparison is a part of the validation logic. Their equality essentially defines the validity of the confirmPassword field and, therefore, must be a part of the validation rules.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Putting those rules into the validation schema is simple. First, we would need to select a field, or a group of fields, by their type or their name. Then, we add a resolver, or a group of resolvers, to that selector.&lt;/p&gt;&lt;p&gt;This is how those criteria would look using a validation schema:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/app/validation-rules.js
import isEmail from &amp;#x27;validator/lib/isEmail&amp;#x27;;
export default {
type: {
email: ({ value }) =&amp;gt; isEmail(value),
password: {
capitalLetter: ({ value }) =&amp;gt; /[A-Z]/.test(value),
oneNumber: ({ value }) =&amp;gt; /[0-9]/.test(value),
minLength: ({ value }) =&amp;gt; (value.length &amp;gt; 5)
}
},
name: {
confirmPassword: {
matches: ({ value, get }) =&amp;gt; {
return (value === get([&amp;#x27;userPassword&amp;#x27;, &amp;#x27;value&amp;#x27;]);
}
}
}
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note how we have managed to declare fairly complex requirements using just a few single-line functions. Now let’s analyze each of them in more detail.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{
type: {
email: ({ value }) =&amp;gt; isEmail(value)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here we select all fields with the &lt;code&gt;type=&amp;quot;email&amp;quot;&lt;/code&gt; and declare the resolver function, which takes the field’s &lt;code&gt;value&lt;/code&gt; and provides it to the third-party &lt;code&gt;isEmail&lt;/code&gt; validator function. The resolver must always return Boolean, which determines the validity of the field.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{
type: {
password: {
capitalLetter: ({ value }) =&amp;gt; /[A-Z]/.test(value),
oneNumber: ({ value }) =&amp;gt; /[0-9]/.test(value),
minLength: ({ value }) =&amp;gt; (value.length &amp;gt; 5)
}
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This selector is similar to the previous one, as we are getting all fields with the &lt;code&gt;type=&amp;quot;password&amp;quot;&lt;/code&gt;, yet instead of a single resolver, we have declared a group of resolver functions, each having its unique name (those are referred to as &amp;quot;&lt;em&gt;named rules&lt;/em&gt;&amp;quot;). This way we can manage several rules related to a single selector and can provide resolver-specific validation messages by their names (&lt;code&gt;capitalLetter&lt;/code&gt;, &lt;code&gt;oneNumber&lt;/code&gt;, &lt;code&gt;minLength&lt;/code&gt;).&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Rule resolvers of the same selector are executed in parallel.&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{
name: {
confirmPassword: {
matches: ({ value, get }) =&amp;gt; {
return (value === get([&amp;#x27;userPassword&amp;#x27;, &amp;#x27;value&amp;#x27;]);
}
}
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This one is the most complex so far. We are taking the field with the &lt;code&gt;name=&amp;quot;confirmPassword&amp;quot;&lt;/code&gt; and declaring a matches named rule. Inside that rule we compare the field’s value to the value prop of the &lt;code&gt;name=&amp;quot;userPassword&amp;quot;&lt;/code&gt; field, using the get function. The get function analyzes the resolver and creates Observable for each referenced field. Whenever the referenced props change, the resolver function is re-evaluated automatically. So, whenever &lt;code&gt;userPassword.value&lt;/code&gt; changes, &lt;code&gt;name=&amp;quot;confirmPassword&amp;quot;&lt;/code&gt; field is re-validated on-the-fly.&lt;/p&gt;&lt;h3 id=&quot;rules-relation&quot;&gt;Rules relation&lt;/h3&gt;&lt;p&gt;A single field may have both type- and name-specific rules associated with it. For example, our &lt;code&gt;confirmPassword&lt;/code&gt; field has both &lt;code&gt;type.password&lt;/code&gt; and &lt;code&gt;name.confirmPassword&lt;/code&gt; rules defined in the schema. It is important to understand the relation between those rules, and the priority of their execution.&lt;/p&gt;&lt;p&gt;The execution of the rules in a validation schema abides by the following principles:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;name&lt;/code&gt; specific rules have a higher priority and, therefore, are executed before the &lt;code&gt;type&lt;/code&gt; specific rules.&lt;/li&gt;&lt;li&gt;Whenever a &lt;code&gt;name&lt;/code&gt; specific rule rejects, no &lt;code&gt;type&lt;/code&gt; specific rules are going to be executed at all.&lt;/li&gt;&lt;li&gt;Sibling rules groups (i.e. rules under &lt;code&gt;type.password&lt;/code&gt;) are executed in parallel, and continue to run regardless of the resolving status of the preceding rule.&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;This is only a brief look at the &lt;a href=&quot;https://redd.gitbook.io/react-advanced-form/validation/logic&quot;&gt;layered validation algorithm&lt;/a&gt; provided by React Advanced Form. Reading through it will help you to understand the logic better, and thus use it more efficiently in your application.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;validation-messages&quot;&gt;Validation messages&lt;/h2&gt;&lt;p&gt;Without being properly reflected in the UI, any validation is useless.&lt;/p&gt;&lt;p&gt;Similar to the validation schema, validation messages reside in the dedicated manifest responsible for describing the rule-message bindings. Declaration of validation messages is very similar to the validation schema: selecting a field by its type or name, and providing the respective messages or message resolvers.&lt;/p&gt;&lt;p&gt;Isolating validation messages has the very same benefits as the validation schema. One of those, for example, is the ability to compose validation messages on runtime, serving different schemas per locale, while keeping the rules intact.&lt;/p&gt;&lt;p&gt;Take a look at the validation messages declaration:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// app/validation-messages.js
export default {
generic: {
missing: &amp;#x27;Please provide the required field&amp;#x27;,
invalid: &amp;#x27;The value you have provided is invalid&amp;#x27;,
},
type: {
email: {
missing: &amp;#x27;Please provide the e-mail&amp;#x27;,
invalid: ({ value }) =&amp;gt; &amp;#x27;The e-mail &amp;quot; + value + &amp;quot; has invalid format&amp;#x27;,
},
password: {
invalid: &amp;#x27;The password you entered is invalid&amp;#x27;,
rule: {
capitalLetter: &amp;#x27;Include at least one capital letter&amp;#x27;,
minLength: &amp;#x27;Password must be at least 6 characters long&amp;#x27;,
},
},
},
name: {
confirmPassword: {
rule: {
matches: &amp;#x27;The passwords do not match&amp;#x27;,
},
},
},
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All validation messages are divided into three groups (listed by their priority):&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;name&lt;/code&gt; — messages applied based on the field’s name.&lt;/li&gt;&lt;li&gt;&lt;code&gt;type&lt;/code&gt; — messages applied based on the field’s type.&lt;/li&gt;&lt;li&gt;&lt;code&gt;generic&lt;/code&gt; — the least specific, fallback messages used when no other specific messages are found.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Each group above can contain the next reserved keys:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;missing&lt;/code&gt; — applied when the field is missing (that is required, but empty).&lt;/li&gt;&lt;li&gt;&lt;code&gt;invalid&lt;/code&gt;— applied when the field is invalid (has unexpected value).&lt;/li&gt;&lt;li&gt;&lt;code&gt;rule&lt;/code&gt;— collection of the messages corresponding to the named validation rules listed in the ruleName: message format.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Messages can be resolved using a plain string, or a resolver function (see &lt;code&gt;type.email.invalid&lt;/code&gt;), which has the same interface as the rule resolver. The difference is that the message resolver must always return a String. This allows to have dynamic validation messages depending on the field props, another fields, or asynchronous validation response, with ease.&lt;/p&gt;&lt;h3 id=&quot;messages-relation-and-fallback&quot;&gt;Messages relation and fallback&lt;/h3&gt;&lt;p&gt;Whenever the higher specific message is provided, it is being used to the respective validity state.&lt;/p&gt;&lt;p&gt;Let’s say our &lt;code&gt;name=&amp;quot;confirmPassword&amp;quot;&lt;/code&gt; field is invalid because its &lt;code&gt;matches&lt;/code&gt; rule has been rejected. This is the order in which the form will attempt to get the corresponding validation message:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;name.confirmPassword.rules.matches&lt;/code&gt; the message directly related to the rejected rule’s name (if it’s a named rule that rejected).&lt;/li&gt;&lt;li&gt;&lt;code&gt;name.confirmPassword.invalid&lt;/code&gt;  the general invalid message related to the invalid field’s name.&lt;/li&gt;&lt;li&gt;&lt;code&gt;type.password.invalid&lt;/code&gt;  the general invalid messages related to the invalid field’s type.&lt;/li&gt;&lt;li&gt;&lt;code&gt;generic.invalid&lt;/code&gt;  the fallback invalid message applicable to any invalid field.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;It is not only the resolving order, but also a fallback sequence for the messages. That means, that whenever a more specific message is not found, the form attempts to get the next message in the specificity order, and return it during the rendering.&lt;/p&gt;&lt;h2 id=&quot;asynchronous-validation&quot;&gt;Asynchronous validation&lt;/h2&gt;&lt;p&gt;Building a modern form will inevitably lead you to the point of asynchronous validation. Unlike the synchronous validation rules that reside in a schema, asynchronous rules are declared on the field components directly, using the &lt;code&gt;asyncRule&lt;/code&gt; prop.&lt;/p&gt;&lt;p&gt;Let’s use it to validate the entered e-mail on-the-fly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#x27;react&amp;#x27;
import { Form } from &amp;#x27;react-advanced-form&amp;#x27;
export default class RegistrationForm extends React.Component {
validateEmail = ({ value, fieldProps, fields, form }) =&amp;gt; {
return fetch(&amp;#x27;https://backend/&amp;#x27;, { body: value })
.then((res) =&amp;gt; res.json())
.then((res) =&amp;gt; {
return {
/* Determine if the e-mail is valid based on response */
valid: res.statusCode === &amp;#x27;SUCCESS&amp;#x27;,
errorCode: res.errorCode,
}
})
}
render() {
return (
&amp;lt;Form&amp;gt;
&amp;lt;Input
name=&amp;quot;userEmail&amp;quot;
type=&amp;quot;email&amp;quot;
asyncRule={this.validateEmail}
required
/&amp;gt;
{/* Rest fields */}
&amp;lt;/Form&amp;gt;
)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice the Object shape returned when the request is resolved. Relying on the Promise status alone is not sufficient to determine the validity of the field. Therefore, there is an explicit valid property to control that. Any additional properties provided to the resolved Object are propagated to async validation message resolver, so the error message can be based on the data received from the validation response:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/validation-messages.js
export default {
...,
name: {
userEmail: {
async: ({ value, errorCode }) =&amp;gt; {
return &amp;#x27;E-mail &amp;#x27; + email + &amp;#x27; is already registered. Error code: &amp;#x27; + errorCode
}
}
}
};
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You may want to consider moving async validation functions into some utils, since those are, essentially, a bunch of pure functions.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;applying-the-validation&quot;&gt;Applying the validation&lt;/h2&gt;&lt;p&gt;In most of the cases we want to apply the validation logic (both rules and messages) application-wide. Of course, being able to customize the validation behavior of a specific form must be possible as well.&lt;/p&gt;&lt;p&gt;With React Advanced Form there are two options, which you can combine:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Use a &lt;code&gt;&amp;lt;FormProvider&amp;gt;&lt;/code&gt; component to wrap your whole application and supply the validation rules and messages to all forms it renders (&lt;em&gt;recommended&lt;/em&gt;).&lt;/li&gt;&lt;li&gt;Provide rules and messages to the &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; component. This way we can extend or completely override the rules exposed by the provider.&lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Combining these two options is perfectly fine.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;For our form we are going to use the first option, and introduce the provider component on the root level of our application:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// app/index.js
import React from &amp;#x27;react&amp;#x27;
import ReactDOM from &amp;#x27;react-dom&amp;#x27;
import { FormProvider } from &amp;#x27;react-advanced-form&amp;#x27;
import rules from &amp;#x27;./validation-rules&amp;#x27;
import messages from &amp;#x27;./validation-messages&amp;#x27;
import Root from &amp;#x27;./Root&amp;#x27;
const renderApp = () =&amp;gt; (
&amp;lt;FormProvider rules={rules} messages={messages}&amp;gt;
&amp;lt;Root /&amp;gt;
&amp;lt;/FormProvider&amp;gt;
)
ReactDOM.render(renderApp, document.getElementById(&amp;#x27;root&amp;#x27;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now all the forms in our application abide by the defined &lt;code&gt;rules&lt;/code&gt; schema and the respective validation &lt;code&gt;messages&lt;/code&gt;.&lt;/p&gt;&lt;h2 id=&quot;interdependent-fields&quot;&gt;Interdependent fields&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;firstName&lt;/code&gt; and &lt;code&gt;lastName&lt;/code&gt; fields of our form are optional. However, it wouldn’t make much sense to allow having one of them blank in case the other is provided. How should the &lt;em&gt;form&lt;/em&gt; handle such a scenario? Let’s take a sneak peek on the feature called &lt;a href=&quot;https://redd.gitbook.io/react-advanced-form/architecture/reactive-props&quot;&gt;reactive props&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Briefly, it is a props change subscription system using RxJS. Whenever some field is referenced within the prop resolver function, that prop’s value is automatically updated whenever the referenced prop changes.&lt;/p&gt;&lt;p&gt;Okay, that sounds complicated. Some example to the rescue:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Input
name=&amp;quot;firstName&amp;quot;
required={({ get }) =&amp;gt; {
// resolves any time &amp;quot;value&amp;quot; prop of &amp;quot;lastName&amp;quot; changes
return !!get([&amp;#x27;lastName&amp;#x27;, &amp;#x27;value&amp;#x27;]);
}} /&amp;gt;
&amp;lt;Input
name=&amp;quot;lastName&amp;quot;
required={({ get }) =&amp;gt; {
// resolves any time &amp;quot;value&amp;quot; prop of &amp;quot;firstName&amp;quot; changes
return !!get([&amp;#x27;firstName&amp;#x27;, &amp;#x27;value&amp;#x27;]);
}} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Note that the getter function returns the &lt;em&gt;actual value&lt;/em&gt; of the prop.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Using simple one-line reactive props resolvers, our &lt;code&gt;firstName&lt;/code&gt; and &lt;code&gt;lastName&lt;/code&gt; fields are interdependent and reactive, while still stateless. Read more on &lt;a href=&quot;https://redd.gitbook.io/react-advanced-form/architecture/reactive-props&quot;&gt;Reactive props&lt;/a&gt; feature to understand its full potential.&lt;/p&gt;&lt;h2 id=&quot;skipping-fields&quot;&gt;Skipping fields&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;confirmPassword&lt;/code&gt; field is beneficial for validation, but its value doesn’t contribute to the serialized object in any way. Being able to skip certain fields during the serialization is such an essential part of the form’s functionality it makes me ashamed we still need to do some workarounds to achieve that.&lt;/p&gt;&lt;p&gt;No more. There is the &lt;code&gt;skip&lt;/code&gt; prop designed for that very purpose. Once provided, a field is bypassed during the serialization process.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Input name=&amp;quot;confirmPassword&amp;quot; type=&amp;quot;password&amp;quot; required skip /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A skipped field behaves just as any other field, meaning it gets validated and can prevent the form from being submitted.&lt;/p&gt;&lt;h2 id=&quot;field-grouping&quot;&gt;Field grouping&lt;/h2&gt;&lt;p&gt;Our registration form is nice and shiny, but what a bummer — we have just got a message from a back-end developer, saying that the fields &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;firstName&lt;/code&gt; and &lt;code&gt;lastName&lt;/code&gt; must be sent under the primaryInfo key.&lt;/p&gt;&lt;p&gt;We’ve all been there. Well, we can easily introduce some custom logic somewhere during the serialization and… Stop! Stop thinking of tweaks and start expecting the form to do more than just field rendering. Use &lt;em&gt;field grouping&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Field grouping allows to control the data structure of the layout level. This significantly reduces the amount of additional transformations when the form layout is not aligned with the API (which, from my experience, happens too often). I believe that it should be possible to tell the serialized structure of the form by simply looking at its layout, without having to venture around in attempts to find where it might have been manually changed.&lt;/p&gt;&lt;p&gt;In our case we would simply add a &lt;code&gt;primaryInfo&lt;/code&gt; field group:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/app/RegistrationForm.jsx
import React from &amp;#x27;react&amp;#x27;
import { Form, Field } from &amp;#x27;react-advanced-form&amp;#x27;
import { Input } from &amp;#x27;...&amp;#x27;
export default class RegistrationForm extends React.Component {
render() {
return (
&amp;lt;Form&amp;gt;
&amp;lt;Field.Group name=&amp;quot;primaryInfo&amp;quot;&amp;gt;
&amp;lt;Input name=&amp;quot;userEmail&amp;quot; type=&amp;quot;email&amp;quot; required /&amp;gt;
&amp;lt;/Field.Group&amp;gt;
&amp;lt;Input name=&amp;quot;userPassword&amp;quot; type=&amp;quot;password&amp;quot; required /&amp;gt;
&amp;lt;Input name=&amp;quot;confirmPassword&amp;quot; type=&amp;quot;password&amp;quot; required /&amp;gt;
&amp;lt;Field.Group name=&amp;quot;primaryInfo&amp;quot;&amp;gt;
&amp;lt;Input name=&amp;quot;firstName&amp;quot; /&amp;gt;
&amp;lt;Input name=&amp;quot;lastName&amp;quot; /&amp;gt;
&amp;lt;/Field.Group&amp;gt;
&amp;lt;button&amp;gt;Register&amp;lt;/button&amp;gt;
&amp;lt;/Form&amp;gt;
)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The layout above will be serialized into the following JSON:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
&amp;quot;primaryInfo&amp;quot;: {
&amp;quot;userEmail&amp;quot;: &amp;quot;...&amp;quot;,
&amp;quot;firstName&amp;quot;: &amp;quot;...&amp;quot;,
&amp;quot;lastName&amp;quot;: &amp;quot;...&amp;quot;
},
&amp;quot;userPassword&amp;quot;: &amp;quot;...&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice how multiple &lt;code&gt;&amp;lt;Field.Group&amp;gt;&lt;/code&gt; components with the same name are automatically merged together upon serialization. Moreover, the fields with the same names under different groups are completely allowed.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Technically, field grouping allows to treat groups as different forms with a single control point. Sending any group to any end-point is now a matter of simply grabbing a proper key in the serialized Object.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2 id=&quot;submit&quot;&gt;Submit&lt;/h2&gt;&lt;p&gt;It’s been a long way, and now we have come to submitting the data.&lt;/p&gt;&lt;p&gt;As clear as this process appears, it is still surprising how outdated and obsolete it is in terms of handling the submit itself. Nowadays form submit must — and I emphasize — &lt;em&gt;must&lt;/em&gt; be handled asynchronously. That is not for the technical advantage alone, but also for a greater performance and user experience.&lt;/p&gt;&lt;p&gt;That being said, it’s safe to assume that submit &lt;code&gt;action&lt;/code&gt; would return an instance of Promise. Once that assumption is made, it becomes easy to handle submit start, success, failure or end relying on the Promise status.&lt;/p&gt;&lt;p&gt;Moreover, it is also essential to validate the form before submitting, and expose some essential information — like the serialized fields — into the &lt;code&gt;action&lt;/code&gt;, as this is something the one would always expect.&lt;/p&gt;&lt;h3 id=&quot;submit-action&quot;&gt;Submit action&lt;/h3&gt;&lt;p&gt;First, we are going to use an action prop to handle the submit of our form. That prop expects a function which returns a Promise. It also provides the data you need during the submit as the arguments to the action function.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#x27;react&amp;#x27;
import { Form } from &amp;#x27;react-advanced-form&amp;#x27;
import { Input } from &amp;#x27;...&amp;#x27;
export default class RegistrationForm extends React.Component {
registerUser = ({ serialized, fields, form }) =&amp;gt; {
return fetch(&amp;#x27;https://backend.dev/user&amp;#x27;, {
method: &amp;#x27;POST&amp;#x27;,
body: JSON.stringify(serialized),
})
}
render() {
return (
&amp;lt;Form action={this.registerUser}&amp;gt;
&amp;lt;Input name=&amp;quot;userEmail&amp;quot; type=&amp;quot;email&amp;quot; required /&amp;gt;
&amp;lt;Input name=&amp;quot;userPassword&amp;quot; type=&amp;quot;password&amp;quot; required /&amp;gt;
&amp;lt;/Form&amp;gt;
)
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You can return a Redux action, as long as it returns a Promise. You would need a dedicated middleware to ensure that (i.e. &lt;a href=&quot;https://github.com/gaearon/redux-thunk&quot;&gt;&lt;code&gt;redux-thunk&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Notice how we are not doing any manual validation or serialization, because we &lt;em&gt;shouldn’t&lt;/em&gt;. Why would any form allow to submit itself without being validated beforehand? Why would any form call a submit handler and don’t provide the serialized fields, when you always need them?&lt;/p&gt;&lt;p&gt;Of course, manual validation and serialization is there when you need it. In other cases expect the form to behave as it must.&lt;/p&gt;&lt;h3 id=&quot;submit-callbacks&quot;&gt;Submit callbacks&lt;/h3&gt;&lt;p&gt;Since we know our action returns a Promise, the form can provide a standardized way to handle the status of that Promise using the respective props. So, instead of chaining the logic directly to the action dispatch, it can be declared in using the callback methods:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;onSubmitStart&lt;/strong&gt;. Dispatched immediately once the submit begins. This is a good place to have any UI loading logic, for example.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;onSubmitted&lt;/strong&gt;. Dispatched when the &lt;code&gt;action&lt;/code&gt; Promise has resolved.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;onSubmitFail&lt;/strong&gt;. Dispatched only when the action has rejected.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;onSubmitEnd&lt;/strong&gt;. Dispatched when the submit is finished, regardless of the Promise status (similar to Bluebird’s &lt;code&gt;.finally()&lt;/code&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Apart from being called at the proper moment, those methods provide useful data through arguments, such as a &lt;code&gt;req&lt;/code&gt; or &lt;code&gt;res&lt;/code&gt; references, as well as the standard callback payload (&lt;code&gt;fields&lt;/code&gt; and &lt;code&gt;form&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://redd.gitbook.io/react-advanced-form/components/form/callbacks&quot;&gt;Read more on submit callback handlers&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;&lt;p&gt;Hence, in a matter of minutes we have created the registration form with the clean layout, multi-layer validation, interdependent &lt;code&gt;required&lt;/code&gt; logic, dealt with the inconsistency between design and the API, and submitted it to the latter. Without having to configure myriad of things. Without any crazy tweaks or hacks. Without even making our form stateful.&lt;/p&gt;&lt;p&gt;The best part is that the most of the code is reusable, which makes the implementation of next forms faster and easier.&lt;/p&gt;&lt;p&gt;Take a look at the working example of our registration form:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://codesandbox.io/embed/n7l025m5y4?module=%2Fsrc%2FRegistrationForm.jsx&quot;&gt;https://codesandbox.io/embed/n7l025m5y4?module=%2Fsrc%2FRegistrationForm.jsx&lt;/a&gt;&lt;/p&gt;&lt;h2 id=&quot;afterword&quot;&gt;Afterword&lt;/h2&gt;&lt;p&gt;That was only a glimpse of what React Advanced Form is to offer. See the &lt;a href=&quot;https://redd.gitbook.io/react-advanced-form&quot;&gt;Official documentation&lt;/a&gt; for more features like custom styling, integration of third-party libraries, controlling various behaviors, and much more.&lt;/p&gt;&lt;p&gt;Of course, no example can match the experience of trying something yourself. Go ahead and give it a try:&lt;/p&gt;&lt;div owner=&quot;kettanaito&quot; repo=&quot;react-advanced-form&quot;&gt;&lt;/div&gt;&lt;p&gt;Your feedback and thoughts are highly appreciated! Thank you.&lt;/p&gt;&lt;h2 id=&quot;materials&quot;&gt;Materials&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/kettanaito/react-advanced-form&quot;&gt;React Advanced Form&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://redd.gitbook.io/react-advanced-form&quot;&gt;Official documentation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://redd.gitbook.io/react-advanced-form/getting-started/installation&quot;&gt;Getting started guidelines&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content:encoded></item></channel></rss>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment