<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Vladislav Ivanov's blog</title>
        <link>https://vladivanov.me/</link>
        <description>My blog about all sorts of technical things and more. Just for fun.</description>
        <lastBuildDate>Tue, 10 Mar 2026 04:08:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>awesome</generator>
        <language>en</language>
        <image>
            <title>Vladislav Ivanov's blog</title>
            <url>https://static.vladivanov.me/meta/logo.png</url>
            <link>https://vladivanov.me/</link>
        </image>
        <copyright>Distributed under the CC BY-NC-SA 4.0 license. All rights reserved 2026, Vladislav Ivanov</copyright>
        <category>Development</category>
        <category>IT</category>
        <category>Technologies</category>
        <category>Front-end</category>
        <category>Blog</category>
        <item>
            <title><![CDATA[Internationalization & localization]]></title>
            <link>https://vladivanov.me/i18n-and-l10n/</link>
            <guid>8547b508-fcfc-4f4a-b236-c1fc358ab156</guid>
            <pubDate>Mon, 28 Aug 2023 17:24:09 GMT</pubDate>
            <description><![CDATA[In this article, I will talk about what I did to make my blog support multiple languages.]]></description>
            <content:encoded><![CDATA[<p>Hello!</p>
<p>I designed this blog to be more serious and formal compared to <a href="https://yungvldai.ru/">my first blog</a>. In order to expand my reach, I decided to write articles in English. However, I still find it easier to formulate some parts of the article in Russian and then translate them into English. As a result, after each publication, there is quite a bit of material left in my workshop that could be polished a bit and published, but unfortunately, the technical capability to do so was not available until recently.</p>
<p>So today, I will tell you about how I upgraded my blog to support multiple languages and also share the challenges I encountered.</p>
<h3>Theory</h3>
<p>A minute of nerdiness, as usual.</p>
<p><b>Internationalization</b> is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes.</p>
<p><b>Localization</b> is the process of adapting internationalized software for a specific region or language by translating text and adding locale-specific components.</p>
<p>In this project I did both.</p>
<h3>Data</h3>
<p>The development (referring to the stage when we've already moved on to writing code) of any feature in my blog starts with defining types. Whether it's component props, <a href="https://en.wikipedia.org/wiki/Data_transfer_object">DTOs</a>, or database models, all of this is described using TypeScript. This approach ensures a rather obvious effect - when something changes in one place, something else might break elsewhere, and typically, the fix is to repair it in desired way to make the feature work.</p>
<p>I'm developing the blog in a <a href="https://en.wikipedia.org/wiki/Monorepo">monorepository</a>, which means both the <code><code class="inline-code">api</code></code> and the <code><code class="inline-code">web-client</code></code> of the service are in the same <a href="https://git-scm.com/">Git</a> repository. Additionally, I have a <code><code class="inline-code">shared</code></code> module where various project-wide constants are defined (such as existing roles and now supported languages), as well as DTOs. As a result, both the controllers in the API and the data-layer in React components use the same types. I want to emphasize that the <code><code class="inline-code">shared</code></code> module is simply a directory from which what's needed is imported. It's not an NPM package or anything else; just a regular folder with TypeScript files.</p>
<p>I use <a href="https://kysely.dev/"><b>Kysely</b></a> SQL query builder for database querying. The usage looks like this:</p>
<pre><code>const { id } = await pg
    .selectFrom(&#039;articles&#039;)
    .select([&#039;id&#039;])
    .where(&#039;slug&#039;, &#039;=&#039;, slug)
    .executeTakeFirst();</code></pre>
<p>This query will retrieve the article's <code><code class="inline-code">id</code></code> based on its <code><code class="inline-code">slug</code></code>. In my opinion, the query looks quite clear and concise, and moreover, <b>Kysely</b> will infer the return type (and even for more complex queries with <code class="inline-code">JOIN</code> or subqueries). Since each controller knows what type it should return to the client, the entire project, despite deploying each application separately, forms a unified system.</p>
<p>This case was no exception. First and foremost, to support multiple languages, I had to make significant changes to the database structure. Everything that can be localized was moved to a separate table, so in the end, each <code><code class="inline-code">Article</code></code> can have as many <code><code class="inline-code">ArticleContent</code></code> entries as needed, but there is a unique constraint for the combination of <code><code class="inline-code">lang</code></code> and <code><code class="inline-code">article_id</code></code>.</p>
<img src="https://static.vladivanov.me/uploads/9d25ad46-74f8-4f5a-b748-0c33ad7f552d.webp" alt="Articles and ArticleContents tables" />
<p>I decided not to create a separate <code><code class="inline-code">Languages</code></code> table, so the <code class="inline-code">lang</code> field is just a <code class="inline-code">varchar</code> where a two-letter language code (according to the <a href="https://www.loc.gov/standards/iso639-2/php/code_list.php">ISO 639-1 standard</a>) is stored. I decided not to do this because adding a language isn't as simple as just inserting a record into the database; that's quite a lot of work, including changes in a source code.</p>
<p>After updating the data schema, almost all controllers depending on it broke. I fixed all the errors and in some places also updated the DTOs. After updating the DTOs, errors started to appear on the client side, and those were also fixed. In fact, the multilingual articles were ready by that time. Although there was still a lot of work ahead in terms of translating the interface in this project, I'll talk about that a bit later.</p>
<h3>Font</h3>
<p>When I was designing this blog, I wanted it to have a clean and formal appearance, perhaps even resembling a printed publication. I tried out several fonts, and my choice fell on <a href="https://fonts.google.com/specimen/Libre+Baskerville**">Libre Baskerville</a>.</p>
<p>Baskerville&nbsp;is a&nbsp;serif&nbsp;typeface&nbsp;designed in the 1750s by&nbsp;John Baskerville&nbsp;(1706–1775) in&nbsp;Birmingham,&nbsp;England.&nbsp;Baskerville is classified as a&nbsp;transitional typeface, intended as a refinement of what are now called&nbsp;old-style&nbsp;typefaces of the period.</p>
<p>Libre Baskerville is a web font optimized for body text. It is based on American Type Founder's Baskerville 1941 but has a higher <a href="https://fonts.google.com/knowledge/glossary/x_height">x-height</a>, wider <a href="https://fonts.google.com/knowledge/glossary/counter">counters</a> and slightly less contrast, making it well-suited for screen reading.</p>
<p>I really like this font, and in my subjective opinion, it suits my blog very well. However, during the design of this feature, it was discovered that Libre Baskerville doesn't support Cyrillic glyphs. This means that when attempting to render text with Russian letters, the font will fallback to a serif font (most likely Times New Roman), and it will look not very good:</p>
<img src="https://static.vladivanov.me/uploads/fcc70f4f-abe5-4715-a813-219a04c8d16a.webp" alt="Old interface with fallback to Times New Roman" />
<p>Of course, this is unacceptable, so I started looking for ways to solve this problem. I tried about a dozen free fonts similar to Libre Baskerville, but my search didn't yield any success. Then I even thought about purchasing a paid version of the Baskerville font on the Paratype website, <a href="https://www.paratype.com/fonts/pt/baskerville-display-pt">for example</a>. I would have to pay $60 for two styles, plus there are additional complexities such as the increasing subscription cost based on pageviews, and a rather strange requirement - "<i>May not use the font to create content by visitors to your websites.</i>" This is strange requirement because in my case, it seems like this requirement restricts the use of the font in comment forms, for example.</p>
<p>In general, there were quite a few questionable and unclear points, and on top of that, I would have to pay, and $60 is relatively a lot, roughly equivalent to about half a year's payment for hosting services. That's why I ruled out this option as well.</p>
<p>In the end, there was nothing left to do but take Libre Baskerville (it's distributed under the <a href="https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;id=OFL">OFL</a>) and add Cyrillic glyphs to it. I thought it wouldn't be too difficult, especially because some letters look the same as Latin ones and could simply be copied. In reality, it's indeed not very difficult, but it's quite a large and painstaking task. Of course, I understand that I haven't experienced creating a font from scratch. I already had ready guidelines from the authors of Libre Baskerville and even some letters for comparison. Honestly, I think I wouldn't be able to create a font entirely from scratch.</p>
<p>So, slowly but surely, I was drawing letter by letter. In the end, I had to draw 33 * 2 (uppercase + lowercase) * 2 (regular + bold) = 132 letters. Some letters I copied without changes, some I created from others (for example, <b>П</b> can easily be made from <b>Н</b>, and <b>Ж</b> from two <b>К</b>s). However, some I had to draw almost from scratch (for example, <b>Ф</b> or <b>Л</b>), which explains why those letters turned out slightly more uneven than the rest. Although, to be honest, all the letters turned out a bit uneven, it's still better than falling back to Times New Roman, and overall, I was satisfied with the result.</p>
<img src="https://static.vladivanov.me/uploads/47dfb59b-d726-432f-80b0-85e2a194ed33.webp" alt="Drawing cyrillic glyphs" />
<p>I used <a href="https://www.glyphrstudio.com/**">Glyphr Studio</a>, and overall, it's quite a convenient program. However, sometimes it has memory leaks and crashes, so I had to save every time after drawing each letter. Unfortunately, I had to redraw some things.</p>
<p>And to you, dear readers, right now, these two wonderful fonts are entirely available for free (licensed under OFL):</p>
<p>- <a href="https://static.vladivanov.me/fonts/BlogBaskerville-Bold.ttf">BlogBaskerville-Bold.ttf</a></p>
<p>-&nbsp;<a href="https://static.vladivanov.me/fonts/BlogBaskerville-Regular.ttf">BlogBaskerville-Regular.ttf</a></p>
<p>- <a href="https://static.vladivanov.me/fonts/OFL.txt">License</a></p>
<h3>Interface</h3>
<p>Dealing with the interface is much simpler. I use <a href="https://www.npmjs.com/package/react-intl"><b>react-intl</b></a> for internationalization. In the code, I use keys, and then, based on the key and the selected language, the appropriate translation is inserted. Additionally, it supports pluralization, value substitution, and formatting for time, dates, numbers, and so on out of the box.</p>
<p>But I want to share my experience, which can be helpful to make work with translations simpler.</p>
<p>The first recommendation is to store translations alongside the component. Let's take the example of the <code><code class="inline-code">ArticleStats</code></code> component (this is a component for displaying article statistics, you might have seen it on the homepage, in the sidebar on each article page, and also in search results):</p>
<pre><code>📦ArticleStats
 ┣ 📂__Icon
 ┃ ┗ 📜ArticleStats__Icon.css
 ┣ 📂__Name
 ┃ ┣ 📜ArticleStats__Name.css
 ┃ ┗ 📜ArticleStats__Name.tsx
 ┣ 📂__Stat
 ┃ ┣ 📜ArticleStats__Stat.css
 ┃ ┗ 📜ArticleStats__Stat.tsx
 ┣ 📂__Value
 ┃ ┣ 📜ArticleStats__Value.css
 ┃ ┗ 📜ArticleStats__Value.tsx
 ┣ 📂ArticleStats.helpers
 ┃ ┗ 📜capitalize.ts
 ┣ 📂ArticleStats.i18n &lt;- directory with translations
 ┃ ┣ 📜ArticleStats.en.json
 ┃ ┗ 📜ArticleStats.ru.json
 ┣ 📜ArticleStats.css
 ┗ 📜ArticleStats.tsx</code></pre>
<p>In the <code><code class="inline-code">ArticleStats.en.json</code></code> file, the keys are stored in the following format:</p>
<pre><code>{
    &quot;ArticleStats.published&quot;: &quot;Published&quot;,
    &quot;ArticleStats.views&quot;: &quot;{views, plural, one {view} other {views}}&quot;,
    &quot;ArticleStats.mins&quot;: &quot;{mins, plural, one {min} other {mins}}&quot;
}</code></pre>
<p>As a result, all translation files for one component are stored in one place, and they can be easily compiled together using a simple Node.js script. Then, this single large file is read on the server, and all translations for all languages are loaded into memory at once. During SSR (server-side rendering), only the keys for the required language are sent to the client. I want to note that all keys are prefixed with the component's name to reduce the likelihood of collisions.</p>
<p>The second piece of advice I could offer is to restrict the use of anything other than string literals when specifying keys. Let me explain. To retrieve a string in <b>react-intl</b>, you must render the <code class="inline-code">FormattedMessage</code> element:</p>
<pre><code>&lt;FormattedMessage id=&quot;ArticleStats.published&quot; /&gt;</code></pre>
<p>or call the <code><code class="inline-code">intl.formatMessage</code></code> function:</p>
<pre><code>intl.formatMessage({ id: &#039;ArticleStats.published&#039; })</code></pre>
<p>And sometimes I've seen examples with template strings and ternary operators, which actually make things more complicated. </p>
<p>For example, instead of:</p>
<pre><code>&lt;FormattedMessage id={`ArticleStats.${action}`} /&gt; // action: &#039;published&#039; | &#039;created&#039; | &#039;updated&#039;</code></pre>
<p>Much better to write:</p>
<pre><code>const actionsMap = {
    published: &lt;FormattedMessage id=&quot;ArticleStats.published&quot; /&gt;,
    created: &lt;FormattedMessage id=&quot;ArticleStats.created&quot; /&gt;,
    updated: &lt;FormattedMessage id=&quot;ArticleStats.updated&quot; /&gt;
};

&lt;div&gt;
    {actionsMap[action]}
&lt;/div&gt;</code></pre>
<p>The code becomes slightly more complex (though not significantly), but now you can search for such calls with regular expressions, not to mention querying the code through the <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">AST</a> (you can experiment with it <a href="https://astexplorer.net/">here</a>&nbsp;by yourself). Static code analysis in this situation is very useful because it allows you to immediately identify which keys are not being used, so you can remove them! The code becomes smaller, bundles become leaner, and there's nothing unnecessary - this is a beauty.</p>
<p>In fact, you can further improve the DX (developer experience) by writing a custom rule for <a href="https://eslint.org/">ESLint</a>. It may not be possible to auto-fix it, but correcting such occurrences will likely be straightforward. In most cases, complex expressions like these can be refactored into a hashmap or a <code class="inline-code">switch-case</code>. If you have just two keys, you can continue to use the ternary operator, but do it outside the message descriptor, not inside it.</p>
<h3>Algorithm</h3>
<p>First and foremost, we check the <code><code class="inline-code">lang</code></code> query parameter. If it exists, and its value matches one of the supported languages, then that specified language is used, and the algorithm stops. Otherwise, we look at the corresponding cookie. If the cookie exists and its value matches one of the supported languages, then the specified language is set. However, if there is no cookie (or an unknown language is specified), we will attempt to parse the <code><code class="inline-code">Accept-Language</code></code> header. If that also fails, or if none of the supported languages are specified in <code><code class="inline-code">Accept-Language</code></code>, English (<code class="inline-code">en</code>) is chosen.</p>
<p>After determining the language, it's set as a "sticky" choice using a cookie. Afterward, the language can only be changed explicitly by visiting a page with the <code><code class="inline-code">lang</code></code> parameter.</p>
<h3>SEO</h3>
<p>A search engine robot should be aware that my website supports multiple languages. When requesting a page without query parameters, GoogleBot (or another search bot) will receive the English version (it doesn't send cookies and the <code class="inline-code">Accept-Language</code> header), which means the algorithm will fallback to <code class="inline-code">en</code>.</p>
<p>To indicate the language of the current page, the <code class="inline-code">lang</code> attribute of the <code class="inline-code">html</code> element is used.</p>
<img src="https://static.vladivanov.me/uploads/3c058729-fce2-4237-ac44-a82673fae251.webp" alt="HTML tag with lang attribute" />
<p>And for links to other pages, <code class="inline-code">link</code> elements with the <code class="inline-code">rel</code>&nbsp;attribute set to&nbsp;<code class="inline-code">alternate</code>&nbsp;are used. It looks like this:</p>
<pre><code>&lt;link rel=&quot;alternate&quot; hreflang=&quot;en&quot; href=&quot;https://vladivanov.me/&quot;&gt; &lt;!-- link to self --&gt;
&lt;link rel=&quot;alternate&quot; hreflang=&quot;en&quot; href=&quot;https://vladivanov.me/?lang=en&quot;&gt;
&lt;link rel=&quot;alternate&quot; hreflang=&quot;ru&quot; href=&quot;https://vladivanov.me/?lang=ru&quot;&gt;</code></pre>
<h3>Deploy</h3>
<p><b>Kysely</b> can generate migrations, but I decided to do everything myself. I manually did both the structural changes and data migration right in the production database container using <b>psql</b>😁. First, I created a new table, set up indexes for searching, dumped the <code class="inline-code">Articles</code> table, then deployed the new version of the service, and only after that dropped the old, unnecessary columns. It was a bit nerve-wracking, even though I think I have weekly backups from DigitalOcean. On the other hand, I've never actually tried to restore from them.</p>
<p>The whole feature was introduced under a feature flag, and regular users couldn't use it. After testing and creating editions in Russian for all articles in the admin office, I removed the feature flag. However, I temporarily disabled language detection based on the <code class="inline-code">Accept-Language</code> header.</p>
<p>Now, along with this article, the feature has started working as described. A language switch button has been added to the interface, and a little later, I will set up an email newsletter for subscribers. This is quite a significant milestone in the development history of my blog.</p>
<h3>Conclusion</h3>
<p>This was an extremely exciting project, considering that it covered all kinds of work. It involved development, database administration, and even design-related aspects like typography. I'm very pleased that my blog now has support of multiple languages, and I'm also happy to share this wonderful experience with you.</p>
<p>Please write in the comments if you're interested in how my blog works; I'm ready to tell you about any part of it.</p>
<p>Thank you for your attention!</p>]]></content:encoded>
            <enclosure url="https://static.vladivanov.me/uploads/bff1d22e-52df-47f7-bb13-139c6461ba73.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Module systems in JavaScript]]></title>
            <link>https://vladivanov.me/module-systems-in-javascript/</link>
            <guid>678e4b7f-86a5-48f7-ac5c-60c2812f2e5c</guid>
            <pubDate>Tue, 20 Jun 2023 14:53:28 GMT</pubDate>
            <description><![CDATA[What are modular systems in JavaScript? Why are there so many? What problems can arise from this and how can they be solved? And what to choose in 2023? I'll tell in this article.]]></description>
            <content:encoded><![CDATA[<p>Hello everyone! 👋</p>
<p>If you have ever had the pleasure of developing something in JavaScript, I believe at least once in your life you have come across something like:</p>
<pre><code>SyntaxError: Cannot use import statement outside a module</code></pre>
<p>(if you haven't, it's still ahead of you 😉)</p>
<p>In particular, it can be challenging to understand what happens when the source code undergoes transpilation during the build process, and one module system transforms into another.</p>
<p>So today, I will talk about JavaScript/TypeScript module systems, how it happened that there are several of them, and personally, what I would recommend using in 2023.</p>
<h3>A bit of theory</h3>
<p>The principle of modularity allows to simplify the task of software design and distribute the development process among groups of developers.</p>
<p>It is the need to develop large-scale software systems that led to the emergence of modular programming, where the entire program is divided into components called modules, each with a manageable size, a clear purpose, and a well-defined interface aka API.</p>
<p>A <b>module </b>in programming&nbsp;is a sequence of logically related code fragments, organized as a separate part of a program. In many languages, it is encapsulated in a separate file or named continuous portion of code.</p>
<p>Of course, the principle of modularity did not bypass JavaScript. On the contrary, JavaScript has implemented several approaches to code organization, but let's take it step by step.</p>
<h3>IIFE</h3>
<p>Let's start, with <a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE"><b>IIFE</b></a>, even though it is not a module system but a native language mechanism. I believe that for the sake of completeness, we need to consider it. <b>IIFE</b> stands for Immediately Invoked Function Expression. It is a function that is declared and immediately invoked. This technique is used for the purpose of isolation - variables declared within the body of such a function are not accessible from the outside, which helps to avoid naming conflicts and pollution of the global namespace. An example of such a function can be seen below:</p>
<pre><code>;(function () {
  const a = 5;

  console.log(a) // 5
})()

console.log(a) // Uncaught ReferenceError: a is not defined</code></pre>
<p>If you prefer to use JS without <code><code class="inline-code">;</code></code>, then I recommend placing a <code><code class="inline-code">;</code></code> before declaring the <b>IIFE</b>, as shown above, to prevent the expression from being interpreted as a function call. However, if you use <code><code class="inline-code">;</code></code>&nbsp;consistently throughout your code, you won't encounter such issues.</p>
<p>By the way, an <b>IIFE</b> can be asynchronous, which means that the body of such a function can use the <code><code class="inline-code">await</code></code> keyword. However, this is not directly relevant to the topic of this article.</p>
<p>Although <b>IIFE</b> provides a convenient (and native) way to isolate code, it does not provide a convenient mechanism for splitting code into files. Of course, you can use as many <code><code class="inline-code">script</code></code> tags as you want on a page (if you develop frontend), but it becomes very difficult to track module dependencies on larger projects. While, from a programming perspective, as I mentioned earlier in the module definition, a module doesn't necessarily have to be a separate file, the practice shows that working with separate files is much easier. As a result, more powerful tools and approaches have emerged over time.</p>
<h3>AMD</h3>
<p><b>AMD</b> (Asynchronous Module Definition) is a module system designed for use in the browser environment. It provides a mechanism for discovering and resolving module dependencies, enabling automatic loading and execution of modules in the correct order.</p>
<p><b>AMD</b>&nbsp;was born out of a group of developers that were displeased with the direction adopted by&nbsp;<b>CommonJS</b>. In fact,&nbsp;<b>AMD</b>&nbsp;was split from&nbsp;<b>CommonJS</b>&nbsp;early in its development.</p>
<p>One of the key features of <b>AMD</b> is its ability to load modules asynchronously. When a module is requested for loading, the <b>AMD</b> loader asynchronously loads the module's dependencies, ensuring the proper loading order.</p>
<p>Here's an example of how it can be used:</p>
<pre><code>define(&#039;sounds&#039;, 
  [&#039;dog&#039;, &#039;audio&#039;], 
  function (dog, audio) {
    return {
      bark: function() {
        return audio.play(dog.getVoice());
      }
    }
  };
});</code></pre>
<p>Here, we are implementing the <code class="inline-code">sounds</code> module and explicitly stating its dependencies. One of the most popular tools that implements <b>AMD</b> is <a href="https://requirejs.org/"><b>RequireJS</b></a>. Here’s an example of how it can be used from official documentation:</p>
<pre><code>requirejs(
  [&#039;helper/util&#039;],
  function(util) {
    // This function is called when scripts/helper/util.js is loaded.
    // If util.js calls define(), then this function is not fired until
    // util&#039;s dependencies have loaded, and the util argument will hold
    // the module value for &quot;helper/util&quot;.
  }
);</code></pre>
<h3>CommonJS (CJS)</h3>
<p>While <b>AMD</b> was used in browser environments, another module system called <b>CommonJS</b> was used in Node.js environments. The main difference between <b>AMD</b> and <b>CommonJS</b> lies in its support for asynchronous module loading.</p>
<p>While <b>AMD</b> is somewhat difficult to come across nowadays, <b>CommonJS</b> is literally everywhere. It's important to note that <b>CommonJS</b> is still the default module system in Node.js.</p>
<p>Here's an example code using <b>CommonJS</b>:</p>
<pre><code>const fs = require(&#039;fs&#039;);
const dog = require(&#039;./dog&#039;);

module.exports = {
  barkToFile: function () {
    fs.writeFileSync(&#039;./bark.txt&#039;, dog.voiceToString());
  }
};</code></pre>
<p>To import modules in <b>CommonJS</b>, the <code><code class="inline-code">require</code></code> function is used. It takes the path (and extension may be omitted) to the module&nbsp;or module name and returns the exported values of that module.</p>
<p>Any properties added to <code><code class="inline-code">exports</code></code> or assigned to <code><code class="inline-code">module.exports</code></code>&nbsp;become the exported values of the module.</p>
<p>In <b>CommonJS</b>, modules are loaded synchronously and resolved at runtime. When a module is initially loaded, its code is executed, and its exported values become available for import in other modules.</p>
<p>In the browser, there is no built-in <code><code class="inline-code">require</code></code> function or access to the file system, so <b>CommonJS</b> is not supported. However, a similar API can be achieved using the <b>AMD</b> approach and the <b>RequireJS</b> library:</p>
<pre><code>define(
  function(require, exports) {
    const dog = require(&quot;dog&quot;);

    exports.barkToConsole = function() {
      console.log(dog.voiceToString());
    }
  }
);</code></pre>
<h3>ES Modules (ESM)</h3>
<p>And here we finally come to the first module system described in the ECMAScript standard. <b>ES modules</b> were introduced with ES6 in 2015. And yes, you understood it correctly, for 20 years there was no standardized module system in JavaScript.&nbsp;Over time, the standard has evolved and I believe you are also familiar with this syntax:</p>
<pre><code>import api from &#039;api.js&#039;;
import dog from &#039;./dog.js&#039;;

export function makeBarkRequest () {
  api.post(&#039;/bark&#039;, (err) =&gt; {
    if (err) return;

    dog.bark();
  });
}</code></pre>
<p>Despite being standardized, <b>ES modules</b> are disabled by default. In a Node.js environment, you need to set <code><code class="inline-code">type</code></code> to <code><code class="inline-code">'module'</code></code> in your package.json or use the <code><code class="inline-code">.mjs</code></code> file extension. In browser environments, to use <b>ESM</b> syntax within a <code><code class="inline-code">script</code></code> tag, you need to set the <code><code class="inline-code">type</code></code> attribute to <code><code class="inline-code">'module'</code></code>. Support for <b>ES modules</b> first appeared in Node.js 12 under the <code><code class="inline-code">--experimental-modules</code></code> flag. In later versions, <b>ES modules</b> were came out from the flag.</p>
<p>That concludes the discussion of module systems, but there are a couple more approaches that are important to look at.</p>
<h3>UMD</h3>
<p><b>UMD</b> (Universal Module Definition) is a template or approach for creating modules that can work in both <b>CommonJS</b> and <b>AMD</b> environments, as well as be accessible as global variables if no module loader is present.</p>
<p>The main idea behind <b>UMD</b> is to create a module that can automatically adapt to different runtime environments and module loaders.</p>
<p><b>UMD</b> uses conditional constructs to determine which existing module system is available and selects the appropriate method for exporting and importing the module. In many cases, it uses <b>AMD</b> as a base and adds special handling to ensure compatibility with <b>CommonJS</b>.</p>
<p>Here's an example of how it typically looks:</p>
<pre><code>(function (root, factory) {
  if (typeof define === &#039;function&#039; &amp;&amp; define.amd) {
    // AMD env
    define([&#039;dependency&#039;], factory);
  } else if (typeof exports === &#039;object&#039;) {
    // CommonJS env
    module.exports = factory(require(&#039;dependency&#039;));
  } else {
    // global
    root.ModuleName = factory(root.Dependency);
  }
}(this, function (dependency) {
  // module logic
  return {
    // exports
  };
}));</code></pre>
<p>If the runtime environment supports <b>AMD</b> (checked using <code><code class="inline-code">define</code></code>), the module is defined using <code><code class="inline-code">define</code></code> and the dependencies are specified for the module loader.</p>
<p>If the runtime environment supports <b>CommonJS</b> (checked using <code><code class="inline-code">exports</code></code>), the module is exported using <code><code class="inline-code">module.exports</code></code> and the dependencies are resolved through <code><code class="inline-code">require</code></code>.</p>
<p>If none of the checks pass, it is assumed that the module is running in a global environment, and it is exported by assigning its property to the global object (<code><code class="inline-code">root</code></code>), which represents the global namespace in this case.</p>
<p><b>UMD</b> allows developers to create modules that can be used in different runtime environments, providing maximum flexibility and code portability.</p>
<h3>SystemJS</h3>
<p><b>SystemJS</b> is a universal JavaScript module loader designed for use in the browser and Node.js runtime. It is designed to support and load various module formats, including <b>AMD</b>, <b>CommonJS</b>, <b>UMD</b>, and <b>ESM</b>, allowing developers to use different module formats within a single project.</p>
<p>Here's an example of how it looks:</p>
<pre><code>System.import(&#039;dog.js&#039;).then(function(module) {
  console.log(module.bark());
}).catch(function(error) {
  console.error(&#039;Failed to load module:&#039;, error);
});</code></pre>
<p>And keep in mind that the <code><code class="inline-code">'dog.js'</code></code>&nbsp;module can be written in ANY of the formats mentioned above. However, I would say that if you find yourself needing to use different module systems within a single project, there might be something wrong with your project.</p>
<p>In real life, you are unlikely to deal with <b>AMD</b>, <b>UMD</b>, or <b>SystemJS</b>. <b>ESM</b> and <b>CJS</b>, on the other hand, are widely used, and you will encounter them regardless of the environment you're developing in.</p>
<h3>Bundlers and traspilers</h3>
<p>In fact, module systems are not something complicated. I believe it won't be difficult to determine the approach being used by examining the source code. However, in real-world development, the code we write doesn't go into production as-is. On the backend, a transpiler is usually sufficient. The most common scenario is transpiling TypeScript code to JavaScript (of course, there are other solutions like <b>ts-node</b> or <b>deno</b>, but that's a topic for another article). On the frontend, things are even more complex - the code goes through not only a transpiler but also various tools called bundlers, minifiers, and so on.</p>
<p>Transpilers (for example,&nbsp;<a href="https://www.npmjs.com/package/typescript"><b>tsc</b></a>) have the ability to generate code using various module systems. <b>tsc </b>supports generating code into all of module systems we've looked at: <b>ESM</b>, <b>CJS</b>, <b>AMD</b>, <b>UMD</b> and <b>SystemJS. </b>And <a href="https://www.typescriptlang.org/play">here</a> you can play with the <b>tsc</b> transpiler by yourself!</p>
<p>Let's see how it works. For example, here's the code:</p>
<pre><code>import dog from &#039;./dog&#039;;
import audio from &#039;./audio&#039;;

export const bark = () =&gt; {
  audio.play(dog.bark())
}</code></pre>
<p>It will not change if you build it with <b>tsc</b> with the option <code class="inline-code">module</code> set to <code class="inline-code">'esnext'</code> , but if you build the code with the same tool but with the <code class="inline-code">module</code> option set to <code class="inline-code">'commonjs'</code> , the code will look like this:</p>
<pre><code>&quot;use strict&quot;;
var __importDefault = (this &amp;&amp; this.__importDefault) || function (mod) {
    return (mod &amp;&amp; mod.__esModule) ? mod : { &quot;default&quot;: mod };
};
Object.defineProperty(exports, &quot;__esModule&quot;, { value: true });
exports.bark = void 0;
const dog_1 = __importDefault(require(&quot;./dog&quot;));
const audio_1 = __importDefault(require(&quot;./audio&quot;));
const bark = () =&gt; {
    audio_1.default.play(dog_1.default.bark());
};
exports.bark = bark;</code></pre>
<p>If we delve into what's happening here and remove the generated helpers, we can notice that the code we wrote using <b>ESM</b> has been transformed into code using <b>CJS</b>, and this is the root cause of all the errors!</p>
<p>This is where the confusion arises. Since you are using <b>ESM</b> in your source files, it seems logical to set the <code><code class="inline-code">type</code></code> to <code><code class="inline-code">'module'</code></code> in package.json. But it's not that simple. In reality, Node.js will run the already compiled <b>tsc</b> code, which, as we have seen, does not use <b>ESM</b>, and you will encounter the error:</p>
<pre><code>ReferenceError: require is not defined</code></pre>
<p>The reverse situation is also possible. For example, if we build the code and it still contains <b>ESM</b> syntax, but we haven't set the <code><code class="inline-code">type</code></code> to <code><code class="inline-code">'module'</code></code> in package.json, we will get the error mentioned at the beginning of the article: </p>
<pre><code>SyntaxError: Cannot use import statement outside a module</code></pre>
<p>All of this is further complicated by the fact that some packages are discontinuing support for <b>CJS</b>. One such package is<b> <a href="https://www.npmjs.com/package/chalk">chalk</a></b>. Starting from major version 5, they stopped supporting <b>CJS</b>, and if you try to use <code><code class="inline-code">require</code></code>, you will encounter the error:</p>
<pre><code>Error [ERR_REQUIRE_ESM]: require() of ES Module</code></pre>
<p>Now, imagine a situation where you have TypeScript code:</p>
<pre><code>import chalk from &#039;chalk&#039;; // &gt;= 5
import dog from &#039;./dog&#039;;

export const colorfulBark = () =&gt; {
  console.log(chalk.green(dog.barkToString()));
}</code></pre>
<p>And you decided to build it with tsc with <code class="inline-code">module</code> option set to <code class="inline-code">'commonjs'</code>. You will get this code:</p>
<pre><code>&quot;use strict&quot;;
var __importDefault = (this &amp;&amp; this.__importDefault) || function (mod) {
    return (mod &amp;&amp; mod.__esModule) ? mod : { &quot;default&quot;: mod };
};
Object.defineProperty(exports, &quot;__esModule&quot;, { value: true });
exports.colorfulBark = void 0;
const chalk_1 = __importDefault(require(&quot;chalk&quot;)); // &lt;-- require(&#039;chalk&#039;)
const dog_1 = __importDefault(require(&quot;./dog&quot;));
const colorfulBark = () =&gt; {
    console.log(chalk_1.default.green(dog_1.default.barkToString()));
};
exports.colorfulBark = colorfulBark;</code></pre>
<p>And when you attempt to run it, you encounter the error </p>
<pre><code>Error [ERR_REQUIRE_ESM]: require() of ES Module</code></pre>
<p>indicating that one of our modules (in this case, <b>chalk</b>) does not support <b>CJS</b>. This can be quite confusing because we don't have any <code><code class="inline-code">require</code></code> statements in our source code! It becomes particularly challenging to troubleshoot in a large project when examining the already bundled code.</p>
<p>In this case, there are two options:</p>
<ol><li>Set type to 'module', which would also require setting module to 'esnext' in the tsconfig (and possibly break other things 😁).</li><li>Downgrade chalk to version 4.1.2, which is the latest version in the 4.x major release that still supports CJS. This means that the module confusion forces you to use older package versions, which is not ideal, of course.</li></ol>
<p>I also want to note that setting <code><code class="inline-code">type</code></code>&nbsp;to <code><code class="inline-code">'module'</code></code>&nbsp;is a very significant project-wide configuration that affects everything, so changing it is usually extremely difficult. For example, if you are using <a href="https://jestjs.io/ru/"><b>Jest</b></a> with <code><code class="inline-code">type</code></code> set to <code><code class="inline-code">'module'</code></code>&nbsp;and then suddenly decide to switch to <code><code class="inline-code">commonjs</code></code> for some reason, your top-level <code><code class="inline-code">await</code></code> in test files will stop working.</p>
<h3>So, what should I do?</h3>
<p>I can't give recommendations on already existing projects, because, as I said, changing the <code class="inline-code">type</code>&nbsp;field is usually very difficult and each case needs to be analyzed separately.&nbsp;Of course, it will be better if you can upgrade to <b>ESM</b>.</p>
<p>As for new projects, I would recommend starting them with <code><code class="inline-code">type</code></code> set to <code><code class="inline-code">'module'</code></code>. This syntax is standard and modern. More and more public packages are dropping support for <b>CJS</b>. Additionally, <code><code class="inline-code">type</code></code> set to <code><code class="inline-code">'module'</code></code> provides additional features like top-level <code><code class="inline-code">await</code></code>.</p>
<p>Of course, there is a caveat. A file extension must be provided when using the <code><code class="inline-code">import</code></code> keyword to resolve relative or absolute specifiers. Directory indexes (for example, <code class="inline-code">.<code>/startup/index.js</code></code><code></code><code></code>) must also be fully specified. This can be a problem if you're using <b>tsc</b> because TypeScript doesn't modify import paths during transpilation. This issue can be resolved using a special tooling or simply by using the <code><code class="inline-code">.js</code></code> extension in imports. TypeScript will still understand that it refers to an appropriate TypeScript file (<code><code class="inline-code">.ts</code></code>), and after transpilation, the code will have a valid path. By the way, my IDE (VSCode) was even able to correctly suggest the path with the <code><code class="inline-code">.js</code></code> extension. And code navigation also works correctly.</p>
<p>As an alternative for TypeScript projects, you can consider using <a href="https://typestrong.org/ts-node/docs/"><b>ts-node</b></a>, but it needs to be run with the <code><code class="inline-code">--transpile-only</code></code> flag. Of course, you need to remember that <b>ts-node</b> is a different runtime from the standard Node.js, and it has its own interesting nuances.</p>
<p>Another solution to this problem could be <a href="https://nodejs.org/api/esm.html#terminology">bare specifiers</a> combined with <code><code class="inline-code">moduleResolution</code></code> set to <code><code class="inline-code">'nodenext'</code></code>. However, you'll also need to solve the issue of copying package.json files to the build folder and it’s important to preserve the directory structure.</p>
<p>Or you can just customize <b>ESM</b> specifier resolution algorithm with <a href="https://nodejs.org/api/esm.html#loaders">loaders API</a> to make modules resolvable without extensions like <b>CJS</b>. But this feature is experimental (Node.js 20).</p>
<p>Nevertheless, for new JavaScript projects, I would still recommend using <code><code class="inline-code">type</code></code> set to <code><code class="inline-code">'module'</code></code>. In the case of TypeScript, it's not as straightforward as we saw above, but I would still recommend using <b>ESM</b> and specifying the <code><code class="inline-code">.js</code></code> extension. Yes, it may seem a bit strange, but it's the best solution among the available options in my opinion.</p>
<p>We discussed the usage of module systems in Node.js environments. But what about browser environments? Usually, in browser environments, these issues don't arise because different tools like <a href="https://webpack.js.org/"><b>webpack</b></a> are used, which inherently support both <b>ESM</b> syntax and <b>CJS</b>. The output is typically a bundle where all the code is simply concatenated into one file, eliminating the need for module systems (and thus avoiding related problems). <b>webpack</b> also provides a solution for splitting applications into files (chunks) for on-demand asynchronous loading, which is also similar to the of module system, but that's a topic for another article.</p>
<p>When it comes to newer tools like <a href="https://vitejs.dev/"><b>vite</b></a>, it is built on <b>ESM</b> and uses <code class="inline-code">script</code> tags in the browser with the attribute <code><code class="inline-code">type</code></code> set to <code><code class="inline-code">'module'</code></code>. <b>vite</b>’s performance is based on <b>ESM</b>. As far as I know, there are no alternatives to this approach, so there is no need to make a choice.</p>
<h3>Conclusion</h3>
<p>While writing this article, I came across numerous other articles on the topic of comparing module systems in JavaScript. However, all of these articles only scratch the surface in terms of examining the differences and similarities of these systems. In contrast, I made an effort to not only explore the module systems but also address the common challenges developers face and even proposed a few solutions. I hope you find this information useful in your projects.</p>
<p>Thank you for your attention, and see you next time!</p>
<h3>References</h3>
<ol><li>Modular programming, viewed 19 Jun 2023, https://en.wikipedia.org/wiki/Modular_programming</li><li>IIFE, viewed 19 Jun 2023, https://developer.mozilla.org/en-US/docs/Glossary/IIFE</li><li>RequireJS, viewed 19 Jun 2023, https://requirejs.org/</li><li>ESM, viewed 19 Jun 2023, https://nodejs.org/docs/latest-v18.x/api/esm.html</li><li>UMD, viewed 19 Jun 2023, https://github.com/umdjs/umd</li><li>AMD, viewed 19 Jun 2023, https://en.wikipedia.org/wiki/Asynchronous_module_definition</li></ol>]]></content:encoded>
            <enclosure url="https://static.vladivanov.me/uploads/2f0f3d0f-828b-4898-8ea3-788d6189510a.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[How to play a QR code]]></title>
            <link>https://vladivanov.me/how-to-play-qr-code/</link>
            <guid>70932b84-34cf-4b74-b091-4071a6ca34c6</guid>
            <pubDate>Fri, 13 Jan 2023 00:55:51 GMT</pubDate>
            <description><![CDATA[Is it possible to make a web game and put it in a QR code?]]></description>
            <content:encoded><![CDATA[<p>Hello!</p>
<p>QR code is a very cool thing. But what if I told you that you can also play them. Interesting? Then let's go!</p>
<p>Today I'm gonna talk about how I wrote a Flappy Bird web game and put it in a QR code. The task is not as simple as it seems at first glance. As a challenge, I want the whole game to be contained inside the QR code and in order to play user only need the QR code. That is, the option with a link to the game inside the QR code is not suitable for me. It is not interesting!</p>
<p>Most often, when you scan a QR code, the data from the decoder gets into the search bar of your default browser. Of course, there are other use cases, but for my game, this scenario seems to be the most suitable. So, the goal is to run the source code of my game from the browser search bar. But is it possible? Yes, it can be implemented with <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs">data URLs</a>. You can test this feature by simply taking any valid HTML code, encode it in base64, prefix it with <code class="inline-code">data:text/html;base64,</code>&nbsp;and paste the resulting string into the browser search bar.</p>
<p>To reach my goal, I just need to encode the string obtained using the algorithm described above into a QR code. It sounds quite simple, but there is one not very pleasant circumstance here: it's impossible to encode an infinite amount of data into a QR code.</p>
<h3>Limitations</h3>
<p>According to the specification, there are 40 QR code versions (1 - 40). Each version has its own configuration and number of dots (modules) that make up the QR code. Version 1 contains 21x21 modules, version 40 has 177x177. From version to version, the size of the code increases by 4 modules per side.</p>
<img src="https://static.vladivanov.me/uploads/12c8126e-35d8-4c5a-9b45-1a133a6a6266.webp" alt="QR code versions comparison" />
<p>Each version corresponds to a certain capacity. It's easy to guess that the more information that needs to be encoded, the higher version of the code is needed.</p>
<p>QR codes support several encoding modes:</p>
<table><tr><td>Mode</td><td>Characters</td><td>Compression</td></tr><tr><td>Numeric</td><td>0, 1, 2, 3, 4, 5, 6, 7, 8, 9</td><td>3 characters are represented by 10 bits</td></tr><tr><td>Alphanumeric</td><td>0–9, A–Z (uppercase only), space, $, %, *, +, -, ., /, :</td><td>2 characters are represented by 11 bits</td></tr><tr><td>Byte</td><td>Characters from the ISO/IEC 8859-1 character set</td><td>Each characters are represented by 8 bits</td></tr><tr><td>Kanji</td><td>Characters from the Shift JIS system based on JIS X 0208</td><td>2 kanji are represented by 13 bits</td></tr></table>
<p>Also, QR codes have a special mechanism for increasing the reliability of storing encrypted information. For codes created with the highest level of reliability, up to 30% of the surface can be damaged or overwritten, but they will retain information and be correctly read. The <a href="https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction">Reed-Solomon algorithm</a> is used to correct errors.</p>
<p>The table below shows the maximum number of storable characters in each encoding mode and for each error correction level.</p>
<table><tr><td>Mode</td><td>L (~7%)</td><td>M (~15%)</td><td>Q (~25%)</td><td>H (~30%)</td></tr><tr><td>Numeric</td><td>7089</td><td>5596</td><td>3993</td><td>3057</td></tr><tr><td>Alphanumeric</td><td>4296</td><td>3391</td><td>2420</td><td>1852</td></tr><tr><td>Byte</td><td>2953</td><td>2331</td><td>1663</td><td>1273</td></tr><tr><td>Kanji</td><td>1817</td><td>1435</td><td>1024</td><td>784</td></tr></table>
<p>The base64 alphabet contains 64 characters and is divided into groups:</p>
<ul><li>Uppercase letters (indices 0-25):&nbsp;A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z</li><li>Lowercase letters (indices 26-51):&nbsp;a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z</li><li>Digits (indices 52-61):&nbsp;0, 1, 2, 3, 4, 5, 6, 7, 8, 9</li><li>Special symbols (indices 62-63):&nbsp;+, /</li></ul>
<p>In addition to these characters, the equal sign (<code>=</code>) is used for padding.</p>
<p>The appropriate encoding mode for base64 encoded data is <b>byte</b>. As for the error correction level, I think the <b>M </b>level will be enough, so I can expect to be able to encode 2331 characters, which is a little over 2 kilobytes. However, it should be noted that the base64 encoded string is about 30% longer than the source one.</p>
<h3>Tools</h3>
<p>For the more efficient development, it's necessary to optimize the process of generating a QR code. Also, it's necessary to perform some preprocessing of the source HTML code, namely <a href="https://en.wikipedia.org/wiki/Minification_(programming)">minification</a>, since I have a strict size limit.</p>
<p>For these purposes, a special NodeJS script was written:</p>
<pre><code>import { minify } from &#039;html-minifier&#039;;
import { readFileSync, writeFileSync } from &#039;fs&#039;;
import { resolve, join } from &#039;path&#039;;
import { toFile } from &#039;qrcode&#039;;
import { execSync } from &#039;child_process&#039;;
import { fileURLToPath } from &#039;url&#039;;

const kb = (bytes) =&gt; (bytes / 1024).toFixed(2);

const __dirname = fileURLToPath(new URL(&#039;.&#039;, import.meta.url));

const input = readFileSync(resolve(__dirname, &#039;../index.html&#039;), { encoding: &#039;utf-8&#039; });

console.log(&#039;Original size is&#039;, kb(input.length), &#039;KB&#039;, `(${input.length} bytes)`);

const minified = minify(input, {
  collapseWhitespace: true,
  minifyCSS: true,
  minifyJS: {
    mangle: {
      // variables declared at the top level 
      // are not minified by default
      toplevel: true
    }
  }
});

console.log(&#039;Minified size is&#039;, kb(minified.length), &#039;KB&#039;, `(${minified.length} bytes)`);

const outDir = resolve(__dirname, &#039;../output&#039;);

execSync(`mkdir -p ${outDir}`);
writeFileSync(join(outDir, &#039;index.min.html&#039;), minified);

const b64 = Buffer.from(minified).toString(&#039;base64&#039;);
const output = `data:text/html;base64,${b64}`;

console.log(&#039;b64 size is&#039;, kb(output.length), &#039;KB&#039;, `(${output.length} bytes)`);

writeFileSync(join(outDir, &#039;b64.txt&#039;), output);
toFile(
  join(outDir, &#039;image.png&#039;), 
  [{ data: output, mode: &#039;byte&#039; }], // encoding mode
  { errorCorrectionLevel: &#039;m&#039; } // errors correction level
);

console.log(&#039;Done!&#039;);</code></pre>
<p>Thus, after running the script, I have a ready-to-use QR code at the output, as well as detailed information by code size, which will help me in further optimization.</p>
<h3>Development</h3>
<p>First, let's create a minimal valid HTML document:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;title&gt;Flappy bird&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>I would like the game to have textures as similar to the original ones as possible. However,&nbsp;the game shouldn't depend on external resources, which means that there is no possibility to download any extras (images, styles, etc). So, I will have to program texture drawing.&nbsp;There are many ways to draw on the web: box-shadows, various gradients, SVGs, canvas, etc. I decided to choose <b>canvas</b>, because it's pretty easy to draw pixel by pixel and it's shorter in code than css or multiple divs:</p>
<pre><code>...
  &lt;body&gt;
    &lt;canvas id=&quot;c&quot; width=&quot;360&quot; height=&quot;640&quot;&gt;&lt;/canvas&gt;
  &lt;/body&gt;
...</code></pre>
<p>I created a DOM node and set id attribute. Now, in code, I can simply refer to it as <code class="inline-code">window.c</code>&nbsp;or just <code class="inline-code">c</code> and there is no need to use methods to find DOM elements in the document like <code class="inline-code">querySelector</code> or <code class="inline-code">getElementById</code>, whose names are actually quite long and cannot be minified. By the way, I would like to draw your attention to this. When using canvas, various methods of <code class="inline-code">CanvasRenderingContext2D</code>&nbsp;are used and it makes sense to assign these methods to variables, because minifier cannot minify the properties (and methods) of an object. But it must be remembered that to assign it's needed to call <code class="inline-code">bind</code>&nbsp;method to pass <code class="inline-code">this</code> context, which in this case is canvas rendering context. Here's what I'm talking about:</p>
<pre><code>let ctx = c.getContext(&#039;2d&#039;);

ctx.fillRect(...)
ctx.fillRect(...)
ctx.fillRect(...)</code></pre>
<p>After minification, it will turn into something like this (whitespace characters are retained for clarity):</p>
<pre><code>let a = c.getContext(&#039;2d&#039;);

a.fillRect(...)
a.fillRect(...)
a.fillRect(...)</code></pre>
<p>While this code:</p>
<pre><code>let ctx = c.getContext(&#039;2d&#039;);

let fillRect = ctx.fillRect.bind(ctx);

fillRect(...)
fillRect(...)
fillRect(...)</code></pre>
<p>Turns into something like this:</p>
<pre><code>let a = c.getContext(&#039;2d&#039;), b = a.fillRect.bind(a);

b(...)
b(...)
b(...)</code></pre>
<p>Profit from such an assignment will be, but not always. For each individual case, it's needed to count the lengths of the strings. For example, for the <code class="inline-code">fillRect</code> method, such an assignment is useful if this function is called more than 2 times.</p>
<p> Of course, there are other ways to assign a function, for example:</p>
<pre><code>let fillRect = (...args) =&gt; ctx.fillRect(...args);</code></pre>
<p>But this option will be longer after minification compared to the code where <code class="inline-code">bind</code> is used. Therefore, I decided to use the code with <code class="inline-code">bind</code>, since I could not find an option that would be shorter.</p>
<p>By the way, <code class="inline-code">let</code> is used everywhere in the code, because it is 2 characters shorter than <code class="inline-code">const</code>.</p>
<h4>Textures</h4>
<p>I've already said that I would like to use textures as close to the original as possible for my game. For example, I would like to see such a bird in the game:</p>
<img src="https://static.vladivanov.me/uploads/987473bf-d968-410c-9ea8-0ceb89b5151c.webp" alt="Bird texture" />
<p>At the same time, there is no way to request pictures from anywhere, so I assumed that the easiest way would be to store images in string variables right in the source code. But how to fit pictures into 2 kilobytes, given that there is also a game logic code?</p>
<p>You can see that in order to draw the texture above, it's only needed 5 colors. While popular image formats such as JPEG or PNG allow millions of colors to be encoded, this is achieved by a fairly high color bit depth. For example, JPEG color images store 24 bits per pixel, and PNG up to 48 bits.&nbsp;</p>
<p>I do not need such a color depth, which means that I can solve this problem by developing my own palette. Let's imagine that a letter of the Latin alphabet has been assigned to each color, then the image above will look like this:</p>
<pre><code>uuuuuuaaaaaauuuuu
uuuuaacccabbauuuu
uuuaccccabbbbauuu
uuacccccabbbabauu
uaccccccabbbabauu
uaaaaacccabbbbauu
adddddacccaaaaaau
adddddaccaeeeeeea
uaaaaaccaeaaaaaau
uuaccccccaeeeeeau
uuuaacccccaaaaauu
uuuuuaaaaauuuuuuu

legend:
u - transparent
a - #000
b - #fff
c - #f71
d - #eec
e - #f20</code></pre>
<p>The size of such a picture is 204 bytes. However, just looking at this picture shows a huge potential for compression! The first thing that comes to mind is <a href="https://en.wikipedia.org/wiki/Run-length_encoding">RLE</a>.&nbsp;Simply put, algorithm replaces repeated characters with a character + number of repetitions: `aaaaa`&nbsp;→ `a5`. In the case of the sequence above, the compression works quite efficiently - 32.35%. The disadvantage of this approach is that for non-repeating sequences, the algorithm works the other way: `abcde`&nbsp;→ `a1b1c1d1e1`, although this can be optimized: `abcdeeeee`&nbsp;→ `-4abcd5e` or `abcdeee` → `abcd3e`.</p>
<p>Let's look at another way. I assumed that there would be no more than 16 colors in the palette, then I can achieve 50% compression for ANY data set without much effort. Each character must be assigned a code. And in the resulting string, write not the character itself, but the result of the expression:</p>
<pre><code>char((code(color1) &lt;&lt; 4) | code(color2))
// char - getting char by its code; code - getting code by char</code></pre>
<p>Then in 8 bits I will store information about 2 pixels. But there is one caveat. Symbol codes must be offset by 2 (i.e. start at <code class="inline-code">0b10</code>). Why? In the ASCII character table, control characters go up to code 32 (32 is space character). I haven't found a way to print them on a Mac, and I'm also not sure if they can be used in a JavaScript code. Most likely, it's possible, but escaping is required. In general, to solve this problem, it's enough to add an offset of 2, and then the result of the <code class="inline-code">code</code> function will always be a number greater than 32, and hence the printable character.</p>
<p>As a result, using the first algorithm (RLE with single character optimization), I got:</p>
<pre><code>u6a6u9a2c3ab2au7ac4ab4au5ac5ab3abau3ac6ab3abau3a5c3ab4au2ad5ac3a6uad5ac2ae6aua5c2aea6u3ac6ae5au4a2c5a5u7a5u7</code></pre>
<p>And as a result of the second:</p>
<pre><code>ÌÌÌ333ÌÌÌÌÃ5U4CÌÌÌÃUU4DCÌÌÃUUSDCCÌÃUUU4D4&lt;Ì335U4DCÌ6ff5U333ÃffcU7wwsÃ33U7333ÌÃUUU7ww&lt;ÌÃ5UU33&lt;ÌÌÌ33&lt;ÌÌÌ</code></pre>
<p>And the difference was only 6 bytes. Of course, I can still try to encode the result of the second algorithm with RLE, but next I have to decode all of this with the script code, which also takes some space, and I decided that RLE suits me perfectly.</p>
<p>By the way, one could try to encode the source string with the <a href="https://en.wikipedia.org/wiki/Huffman_coding">Huffman algorithm</a>, and even calculate the Huffman tree in advance, but in this case it's rather difficult to control the non-appearance of control non-printable characters, and the decoder implementation code is quite cumbersome compared to the RLE decoder code or bit sequences.</p>
<p>Thus, textures have been created:</p>
<pre><code>let birdTexture = `a6u8a2c3ab2au6ac4ab4au4ac5ab3abau2ac6ab3abau2ac7ab4au2ac8a6uac7ad6auac5ada6u2ac6ad5au3a2c5a5u6a5`;
let wing1Texture = `a4u2ae4auae5a2e5auae3au3a3`;
let wing2Texture = `a5uae5a2e5aua5u`;
let wing3Texture = `a5uae5a2e4auae3au3a4`;
let pipeTexture = &#039;g6h4g4h9h3g4h4g9g4f9f3&#039;;
let widePipeTexture = &#039;g3&#039; + pipeTexture + &#039;f3&#039;;</code></pre>
<p>After minification, the size of this code will be about 220 bytes, which is quite acceptable.</p>
<p>But for now, these are just variables of type <code class="inline-code">string</code>. To turn them into images on screen, It's needed to do certain actions:</p>
<pre><code>let draw = (t, x, y, w, i = 0, pw = 2, ph = 2) =&gt; {
  while (t.length) {
    let c = t[0], times = +t[1]; 
    // I hope that there are no more than 9 repetitions
    // but if even more, then I just write a5a5 instead of a10
    fillStyle(c);
    t = t.slice(times !== times ? 1 : 2);

    if (c === &#039;u&#039;) i += (times || 1);
    else for (let until = (times || 1) + i; i &lt; until; i++) fillRect(x + (i % w) * pw, y + ((i / w) | 0) * pw, pw, ph);
  }
}</code></pre>
<p>So what's going on here? The function takes the texture (one of those strings that we discussed above) as the first argument. Next come the <code class="inline-code">x</code> and <code class="inline-code">y</code> coordinates - this is the drawing offset relative to the upper left corner of the canvas. Since the texture is represented by a string, it's needed to specify <code class="inline-code">w</code> (width), this sets the amount of pixels to be drawn before moving to the "next line". The <code class="inline-code">i</code> argument is used for the purpose of optimization - if the texture starts with empty pixels <code class="inline-code">'u'</code>, then they can be omitted, and the <code class="inline-code">i</code> counter value is started from the number of such pixels. It's like getting rid of trailing zeros in a number. <code class="inline-code">pw</code>, <code class="inline-code">py</code> - pixel width and height, respectively. Since most of the pipe looks like this:</p>
<img src="https://static.vladivanov.me/uploads/db41b856-4e2e-4cb6-a05e-f924ecfe5461.webp" alt="Pipe texture" />
<p>I can call <code class="inline-code">draw</code> just once and specify <code class="inline-code">ph</code> = height of the pipe, instead of N draw calls, where N is the height of the pipe in pixels / <code class="inline-code">ph</code>. Made for performance.</p>
<h4>Code</h4>
<p>The whole game (like any other AFAIK) will be in an infinite loop:</p>
<pre><code>let tick = 0;

let setup = () =&gt; {
  vSpeed = flyUpSpeed; // at the moment the game starts, the bird flies up
  score = playing = 0;
  y = 308; // place the bird vertically in the center
  pipes = [[100, gate()], [788, gate()]]; 
  // initialize the first two pipes,
  // more about pipes above
}

let render = () =&gt; {
  ...

  tick++;
  requestAnimationFrame(render);
}

setup();
render();</code></pre>
<p>The first step, of course, is to draw the background. I just fill it with <code class="inline-code">#0ac</code> color. Next comes the drawing of pipes:</p>
<pre><code>pipes.map(pipe =&gt; {
  pipe[0] -= hSpeed; // move pipes to the left every render

  let [px, py] = pipe;

  draw(&#039;a2&#039; + pipeTexture + &#039;a2&#039;, px, 0, 64, 0, 1, height);
  fillStyle(&#039;a&#039;);
  fillRect(px - 3, py - 32, 69, 284);
  draw(widePipeTexture, px - 1, py - 30, 66, 0, 1, 28);
  draw(widePipeTexture, px - 1, py + 222, 66, 0, 1, 28);
  fillStyle(&#039;i&#039;);
  fillRect(px - 3, py, 69, 220);

  ...
})</code></pre>
<p>The memory contains information about only two pipes, because it simply won't fit on the screen anymore. The game is designed so that when the pipe with index = 0 goes off the screen, it is removed, and the player gets a point:</p>
<pre><code>if (px &lt; -64) {
  score++;
  bestScore = score &gt; bestScore ? score : bestScore; // ternary operator is shorter than Math.max
  pipes = [...pipes.slice(1), [pipes[1][0] + 284, gate()]];
}</code></pre>
<p>An information about pipes is stored as an array of <code class="inline-code">[x, y]</code> tuples, where <code class="inline-code">x</code> is the distance from the left edge of the canvas to the beginning of the pipe, <code class="inline-code">y</code> is the distance from the top of the screen to the end of the top pipe (then comes gap, and then the bottom pipe starts). The <code class="inline-code">y</code> for each pipe is generated at the time of creation by the <code class="inline-code">gate</code> function:</p>
<pre><code>let gate = () =&gt; (Math.random() * 292 + 64) | 0;</code></pre>
<p>The distances are calculated in such a way that the player cannot crash into a pipe with an index other than 0, so the collision check is carried out for only one pipe (index = 0):</p>
<pre><code>let [[px, py]] = pipes; // take coordinates of pipe with index = 0

if ((px &lt; 198 &amp;&amp; px &gt; 98 &amp;&amp; (y &lt; py || py + 188 &lt; y)) || (y &lt; 0 || y &gt; 616)) {
  // game over
  setup();
}</code></pre>
<p>By the way, as you have already noticed, all the constants are calculated in advance, because this way the code takes up less space.</p>
<p>After the pipes, a bird is drawn. It always has the same <code class="inline-code">x</code>, but <code class="inline-code">y</code> changes every render:</p>
<pre><code>let gravityAcceleration = .5;
let flyUpSpeed = -12;

vSpeed += gravityAcceleration;
y += vSpeed;

...

draw(birdTexture, 164, y, 16, 5);

// draw animated wings above the bird
let wingsPhase = ((tick / 4) | 0) % 4;

wingsPhase % 2 
  ? draw(wing2Texture, 162, y + 10, 7, 1) 
  : !wingsPhase
  ? draw(wing1Texture, 162, y + 6, 7, 1) 
  : draw(wing3Texture, 162, y + 12, 7, 1);

...

c.onclick = () =&gt; { 
  // fly up on click
  vSpeed = flyUpSpeed;
}</code></pre>
<p>Lastly, text is drawn to tell the player what he is playing at all, how many points he has scored and show the game over screen:</p>
<pre><code>fillStyle(&#039;b&#039;);
fillText(`Score: ${score} | Best: ${bestScore}`, 26);

if (!playing) {          
  fillText(played ? &#039;Game over&#039; : &#039;Flappy bird&#039;, 210, 32);
  fillText(&#039;Click to play&#039; + (played ? &#039; again&#039; : &#039;&#039;), 430);

  !played &amp;&amp; fillText(&#039;QR code edition&#039;, 230, 12, 246);
}</code></pre>
<p>I showed you almost all the code, however, I still omitted some things, but you can find the full code of the project and even play around on the <a href="https://github.com/yungvldai/flappy-bird">GitHub project page</a>.</p>
<h3>Building</h3>
<p>After code is completed, I can start building. Thanks to the fact that I took care of the tools in advance, now I only need to run:</p>
<pre><code>npm i &amp;&amp; npm run build</code></pre>
<p>The script itself will create the output folder and put the result there, namely:</p>
<ol><li>Minified HTML;</li><li>Data URL;</li><li>QR code PNG image.</li></ol>
<p>Also script prints size details:</p>
<pre><code>Original size is 3.82 KB (3907 bytes)
Minified size is 1.53 KB (1569 bytes)
b64 size is 2.06 KB (2114 bytes)
Done!</code></pre>
<p>And you can scan the QR code right now:</p>
<img src="https://static.vladivanov.me/uploads/4fb1fc2d-58da-423a-87f1-326a41c4fa63.webp" alt="Resulting QR code with a game inside" />
<p> or copy+paste the data URL into your browser search bar:</p>
<pre><code>data:text/html;base64,PGh0bWw+PGhlYWQ+PHRpdGxlPkZsYXBweSBCaXJkPC90aXRsZT48bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSxtYXhpbXVtLXNjYWxlPTEsdXNlci1zY2FsYWJsZT0wIj48L2hlYWQ+PGJvZHkgc3R5bGU9ImJhY2tncm91bmQ6IzAwMCI+PGNhbnZhcyBzdHlsZT0iaGVpZ2h0Ojk2JTtwb3NpdGlvbjpmaXhlZDt0b3A6NTAlO2xlZnQ6NTAlO3RyYW5zZm9ybTp0cmFuc2xhdGUzZCgtNTAlLC01MCUsMCkiIGlkPSJjIiB3aWR0aD0iMzYwIiBoZWlnaHQ9IjY0MCI+PC9jYW52YXM+PHNjcmlwdD5sZXQgaT1jLmdldENvbnRleHQoIjJkIiksdT0oaS50ZXh0QWxpZ249ImNlbnRlciIsYy5oZWlnaHQpLGU9e2E6IjAwMCIsYjoiZmZmIixjOiJmNzEiLGQ6ImYyMCIsZToiZWVjIixmOiIxNzAiLGc6IjFhMCIsaDoiMWQwIixpOiIwYWMifSxnPWkuZmlsbFJlY3QuYmluZChpKSx0PShhLGUsYz0xNix1PTE4MCk9PntpLmZvbnQ9YysicHggY291cmllciIsaS5maWxsVGV4dChhLHUsZSl9LG49YT0+aS5maWxsU3R5bGU9IiMiK2VbYV0sbD0iYTZ1OGEyYzNhYjJhdTZhYzRhYjRhdTRhYzVhYjNhYmF1MmFjNmFiM2FiYXUyYWM3YWI0YXUyYWM4YTZ1YWM3YWQ2YXVhYzVhZGE2dTJhYzZhZDVhdTNhMmM1YTV1NmE1IixmPSJhNHUyYWU0YXVhZTVhMmU1YXVhZTNhdTNhMyIscj0iYTV1YWU1YTJlNWF1YTV1IixvPSJhNXVhZTVhMmU0YXVhZTNhdTNhNCIsYj0iZzZoNGc0aDloM2c0aDRnOWc0ZjlmMyIsZD0iZzMiK2IrImYzIixoLG0scCxzLHYseD0wLHk9MCxrPTQsQT0uNSxhPS0xMixDPTAsRj0oKT0+MjkyKk1hdGgucmFuZG9tKCkrNjR8MCxSPShhLGUsYyx1LGk9MCx0PTIsbD0yKT0+e2Zvcig7YS5sZW5ndGg7KXt2YXIgZj1hWzBdLHI9K2FbMV07aWYobihmKSxhPWEuc2xpY2UociE9cj8xOjIpLCJ1Ij09PWYpaSs9cnx8MTtlbHNlIGZvcih2YXIgbz0ocnx8MSkraTtpPG87aSsrKWcoZStpJXUqdCxjKyhpL3V8MCkqdCx0LGwpfX0sUz0oKT0+e209YSxwPXY9MCxoPTMwOCxzPVtbNTA0LEYoKV0sWzc4OCxGKCldXX0scT0oKT0+e24oImkiKSxnKDAsMCwzNjAsNjQwKSx2JiYobSs9QSxoKz1tLHMubWFwKGE9PnthWzBdLT1rO3ZhclthLGVdPWE7UigiYTIiK2IrImEyIixhLDAsNjQsMCwxLHUpLG4oImEiKSxnKGEtMyxlLTMyLDY5LDI4NCksUihkLGEtMSxlLTMwLDY2LDAsMSwyOCksUihkLGEtMSxlKzIyMiw2NiwwLDEsMjgpLG4oImkiKSxnKGEtMyxlLDY5LDIyMCksYTwtNjQmJihwKyssQz1wPkM/cDpDLHM9Wy4uLnMuc2xpY2UoMSksW3NbMV1bMF0rMjg0LEYoKV1dKX0pLFtbZSxhXV09cyxlPDE5OCYmOTg8ZSYmKGg8YXx8YSsxODg8aCl8fGg8MHx8NjE2PGgpJiZTKCksUihsLDE2NCxoLDE2LDUpO3ZhciBhLGU9KHkvNHwwKSU0O2UlMj9SKHIsMTYyLGgrMTAsNywxKTplP1IobywxNjIsaCsxMiw3LDEpOlIoZiwxNjIsaCs2LDcsMSksbigiYiIpLHQoYFNjb3JlOiAke3B9IHwgQmVzdDogYCtDLDI2KSx2fHwodCh4PyJHYW1lIG92ZXIiOiJGbGFwcHkgYmlyZCIsMjEwLDMyKSx0KCJDbGljayB0byBwbGF5IisoeD8iIGFnYWluIjoiIiksNDMwKSx4KXx8dCgiUVIgY29kZSBlZGl0aW9uIiwyMzAsMTIsMjQ2KSx5KysscmVxdWVzdEFuaW1hdGlvbkZyYW1lKHEpfTtTKCkscSgpLGMub25jbGljaz0oKT0+e209YSx2PXg9MX08L3NjcmlwdD48L2JvZHk+PC9odG1sPg==</code></pre>
<p>And play <b>Flappy Bird QR code edititon</b>! And the coolest thing is that the game does not need anything other than a QR code (well, device with a browser, of course).</p>
<p>And here's a screenshot of the resulting game. However, I recommend you to play, it's funny!</p>
<img src="https://static.vladivanov.me/uploads/faae9eb7-b531-44cc-9f8e-1b1c3b49c87e.webp" alt="Playing demo" />
<p>Thank you for reading!</p>
<p>⚠️ Alas, the standard iPhone camera does not understand the data URLs. It recognizes the QR code but says "no usable data found". I managed to recognize the code with the first application from the App Store, but in general there should be no problems with any other application.</p>
<p>🤓 At the very beginning, I told you about the versions of QR codes. So, resulting QR code version is 39.</p>
<h3>References</h3>
<ol><li>qrcode, viewed 12 Jan 2023 &lt;https://www.npmjs.com/package/qrcode&gt;</li><li>QR Code Specification, viewed 12 Jan 2023, &lt;https://www.labeljoy.com/qr-code/qr-code-specification/&gt;</li><li>QR code, viewed 12 Jan 2023, &lt;https://en.wikipedia.org/wiki/QR_code&gt;</li><li>Data URLS, MDN, viewed 12 Jan 2023, &lt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs&gt;</li><li>Base64 Characters, viewed 12 Jan 2023, &lt;https://base64.guru/learn/base64-characters&gt;&nbsp;</li></ol>]]></content:encoded>
            <enclosure url="https://static.vladivanov.me/uploads/53f9a25c-0d47-40bb-9c32-17cbbc7db3ba.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[CSS: From Chaos to Order]]></title>
            <link>https://vladivanov.me/css-from-chaos-to-order/</link>
            <guid>a766aa9b-2668-4997-ba27-cb880f8b082a</guid>
            <pubDate>Fri, 23 Dec 2022 21:35:47 GMT</pubDate>
            <description><![CDATA[I talk about my experience with tidying up styles across a project, as well as the tools that have been developed to do so.]]></description>
            <content:encoded><![CDATA[<h3>Intro</h3>
<p>Hello! If you are a web developer or have ever had the pleasure of writing a frontend, you must know what CSS is. If you've ever written a front-end for a fairly large web service, you've probably come across the fact that when you open a file with styles which were written a year ago, you can't understand anything at all!</p>
<p>Understanding large CSS files is quite challenging.&nbsp;And different preprocessors do not improve the development experience, but rather make it more difficult.&nbsp;I highly recommend not using preprocessors in 2022. CSS has a lot of power and in my opinion preprocessors are not worth the build time added and the cognitive complexity added by the SASS/SCSS/etc syntax.&nbsp;The problem with large CSS files cannot be solved in any way except for adequate code separation.</p>
<p>Another common problem with CSS is the fact that sometimes it is very difficult to tell from the name of the class about appearance and meaning of the element to which it will be applied.&nbsp;</p>
<p>And&nbsp;<a href="https://en.bem.info/methodology/quick-start/">BEM methodology</a> was once invented to solve both problems. I know that many people consider it obsolete and, to be honest, six months ago I thought the same way. However, you just need to understand the essence and you will immediately change your mind! BEM is not about naming, as it might seem, it is about the logic of splitting the interface into component parts, called <b>BEM entities</b>.</p>
<p>And actually if you follow the BEM methodology and some convention about splitting source code files, you can come to a utopian world of the future, where there are styles for ONE element in ONE file, and when reading the code, there is no reaction like this:</p>
<img src="https://static.vladivanov.me/uploads/baedbeba-95cc-481b-9259-4bd9af0adf35.webp" alt="Meme with Sheldon Cooper "Why?"" />
<p>But what if we go further and consolidate such conventions not in words and in linters, but in the very essence of writing styles?&nbsp;That is what I propose to talk about today.</p>
<h3>Main idea</h3>
<p>You probably know what <a href="https://github.com/webpack-contrib/css-loader">css-loader</a> is and how it allows you to work with <a href="https://github.com/css-modules/css-modules">CSS modules</a>. If this is not the case, then let me remind you what it is using the example of a React component:</p>
<pre><code>import styles from &#039;./Button.css&#039;;

export const Button = ({ children }) =&gt; {
  return &lt;button type=&quot;button&quot; className={styles.Button}&gt;{children}&lt;/button&gt;;
}</code></pre>
<p>Convenient, isn't it?</p>
<p>If the development is carried out using the BEM methodology, then it may look something like this:</p>
<pre><code>import cls from &#039;classnames&#039;;
import styles from &#039;./Button.css&#039;;

// type: primary | secondary

export const Button = ({ children, type }) =&gt; {
  return (
    &lt;button 
      type=&quot;button&quot; 
      className={cls(styles.Button, {
        [styles.Button_type_primary]: type === &#039;primary&#039;,
        [styles.Button_type_secondary]: type === &#039;secondary&#039;
      })}
    &gt;
      {children}
    &lt;/button&gt;
  );
}</code></pre>
<p>In BEM, such entities are called <b>modifiers</b>. Usually, they are used to give a certain look to an element. In the example above, the button could change color depending on its <code class="inline-code">type</code>.</p>
<p>Also, it is not difficult to guess that in order to work with modifiers, we need some kind of auxiliary tool so as not to manually concatenate classnames. In the example above, the <a href="https://www.npmjs.com/package/classnames">classnames</a> NPM package serves as such a tool. Pay attention to popularity - <i><b>11</b>M downloads/week</i>.</p>
<p>Wait! But after all, if we have specific naming rules, can't we ourselves write a function by analogy with <code class="inline-code">cls</code>, which could include certain modifiers, depending on the options passed, but with a more convenient API for our case. Let's try:</p>
<pre><code>import styles from &#039;./Button.css&#039;;

const cls = (base, mods) =&gt; {
  let names = Object.entries(mods).map(([modName, modVal]) =&gt; {
    return `${base}_${modName}_${modVal}`;
  });

  return names.concat([base]).join(&#039; &#039;);
}

// type: primary | secondary

export const Button = ({ children, type }) =&gt; {
  return (
    &lt;button 
      type=&quot;button&quot; 
      className={cls(styles.Button, { type })}
    &gt;
      {children}
    &lt;/button&gt;
  );
}</code></pre>
<p>It looks cool! But it is inconvenient that you need to carry the <code class="inline-code">cls</code> function around with you. In addition, it is very difficult to foresee all scenarios for the use of such a function. For example, if the BEM modifier is boolean, then the value of the <code class="inline-code">true</code> modifier can be omitted and just a name can be used, such as <code class="inline-code">'Button_disabled'</code>.</p>
<p>Well, meet!</p>
<h3>Implementation</h3>
<p>To get started, just watch a small demo:</p>
<img src="https://static.vladivanov.me/uploads/703eea95-b648-49af-9251-b052b26ec005.webp" alt="Functional BEM demo" />
<p>In a nutshell, everything that I wrote about above is merged together, and TypeScript support has also been added.</p>
<p>All this is called functional BEM and you can learn more about the project <a href="https://github.com/yungvldai/fbem">here</a>.&nbsp;All <code class="inline-code">@fbem</code> packages are open-source projects and distributed under the MIT license, which means that you can use them in your projects right now.&nbsp;</p>
<p>The project is based on a <a href="https://github.com/yungvldai/fbem/tree/master/packages/css-loader">special loader</a>&nbsp;for <b>webpack&nbsp;</b>named&nbsp;<b>@fbem/css-loader</b>, which allows you to import special BEM functions from CSS files. A BEM function always has two arguments: modifiers object, where key is the name of the modifier and value is its value; the second one is an array of mixes (a term from the BEM methodology). It's just an array of strings that will be added to the end of the resulting classname.</p>
<p><b>@fbem/css-loader</b> is very similar to <a href="https://github.com/webpack-contrib/css-loader">css-loader</a> (and actually a fork of it) and has almost the same set of options and functionality.&nbsp;You can even use it with different style preprocessors by simply chaining loaders together in webpack config. But be sure to pick up if you need them 😏.</p>
<p>As I wrote above, it is more convenient to work with CSS when it is adequately divided into files. Even more convenient when there is a rule "<i>one file - styles for one DOM node</i>".</p>
<p>For these purposes, a special <a href="https://github.com/yungvldai/fbem/tree/master/packages/utils">package</a> was developed, which contains only one function - <code class="inline-code">compose</code>.&nbsp;This function allows you to combine several BEM functions into one and to split styles into as many files as you want. Take a look:</p>
<pre><code>import { compose } from &#039;@fbem/utils&#039;;

import { cnButton as modDisabled } from &#039;./_disabled/button_disabled.css&#039;;
import { cnButton as modStyle } from &#039;./_style/button_style.css&#039;;
import { cnButton as base } from &#039;./button.css&#039;;

const cnButton = compose(base, modDisabled, modStyle);

cnButton({ style: &#039;flat&#039;, disabled: true }, [&#039;mix&#039;]); // &#039;button button_style_flat button_disabled mix&#039;</code></pre>
<h3>Conclusion</h3>
<p>In this article I showed a method (and tools for implementing this method) of organizing styles in a project.&nbsp;More information can be found in the <a href="https://github.com/yungvldai/fbem">project's GitHub repository</a>. I hope I managed to interest you in my experiments in optimizations of writing styles process.&nbsp;Also, I hope you enjoyed the article and would be grateful if you could share it with someone. </p>
<p>Thank you.</p>]]></content:encoded>
            <enclosure url="https://static.vladivanov.me/uploads/c545e869-3283-4ef7-baf8-9ef8fb5e98c2.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[About]]></title>
            <link>https://vladivanov.me/about/</link>
            <guid>e1bc8417-8064-4321-a9ee-aabe32a1cfe3</guid>
            <pubDate>Fri, 16 Dec 2022 22:28:32 GMT</pubDate>
            <description><![CDATA[A small article about who I am and why I created this blog.]]></description>
            <content:encoded><![CDATA[<p><b>Hello👋,</b>&nbsp;I'm Vladislav Ivanov. I graduated from Faculty of Control Systems and Robotics at ITMO University, now I work as a front-end developer at TripleTen (<a href="https://tripleten.com/">https://tripleten.com/</a>).</p>
<p>I started blogging instead of a practice diary in university. Then I just liked to write articles on different topics. Mostly technical. Here is my <a href="https://yungvldai.ru/">old blog</a>.&nbsp;Now I want to change the format a bit, so I created this brand new blog. </p>
<p>If you have any suggestions, complaints or wishes, feel free to mail on: <a href="mailto:me@vladivanov.me">me@vladivanov.me</a>.</p>]]></content:encoded>
            <enclosure url="https://static.vladivanov.me/uploads/15ee49b0-fa97-498c-bb15-2815987539f3.webp" length="0" type="image/webp"/>
        </item>
    </channel>
</rss>