JavaScript https://omedia.dev/ en WebRTC 101: How Real-time Video Communication is Facilitated https://omedia.dev/blog/webrtc-101-how-real-time-video-communication-facilitated <span>WebRTC 101: How Real-time Video Communication is Facilitated</span> <span><span lang="" about="/user/59" typeof="schema:Person" property="schema:name" datatype="">Giorgi Beruashvili</span></span> <span>Fri, 01/27/2023 - 13:51</span> <div class="f-body om-text-content"> <p>Real-time video communication has become an essential part of our daily lives, whether it's for work or socializing. The ability to have face-to-face conversations with anyone, anywhere, has been made possible by the development of WebRTC, a technology that allows for real-time video and audio communication in the browser.</p> <p>The process of connecting two users starts with locating each other using their public IP addresses, but this can be challenging due to the limited number of public IP addresses available. Private IP addresses were introduced to solve this problem, and NAT and STUN/TURN servers are used to assign public IP addresses and establish direct connections. Additionally, ICE and SDP protocols are used to standardize the exchange of information about media streams, inputs, and outputs.</p> <p>Let's start with the basics. Consider the scenario in which person A wants to call person B. The first step is to locate each other. If we imagine an empty Cartesian coordinate system, we need to locate two dots (A and B) in order to connect them with a line. In the internet world, their locations are their public IP addresses, which determine their public presence.</p> <p>When public IP addresses were first introduced, they were formatted using a 32-bit system, which mathematically means that there could only be 2 to the power of 32 instances of public IP addresses, approximately 4.2 billion possible public IP addresses. However, nowadays there are more internet users and connected devices than the number of possible public IP addresses. As a result, not every device can have its own public IP address and therefore cannot have a public presence, meaning no dots on the Cartesian system.</p> <p>How do we solve this issue?</p> <p>To solve this problem, private IP addresses were introduced. Generally, only routers have public IP addresses, and all devices connected to this particular router have their own private IP address. These private IP addresses cannot communicate with the external world; they cannot send a request to the internet or receive a response.</p> <p>When they send a request to the router, they are given a public presence (public IP address) through a process called <strong>NAT</strong> (Network Address Translation). This request is then sent to the destination and a response is returned, which is translated back to the private IP address that sent the request.</p> <p>There are four types of NAT:</p> <ol> <li>Full-cone NAT</li> <li>IP address restricted NAT</li> <li>Port restricted NAT</li> <li>Symmetrical NAT.</li> </ol> <p>Most routers operate with the full-cone NAT system, which allows responses from all IP addresses and ports to come through. Other types, however, check the trustworthiness of the response-issuer's IP address, port, or both. This will be important later on in the process, but for now we know that we send a request from a private IP address which is wrapped by a public IP address using NAT. The request receives a response from the destination and either passes through (full-cone) or is checked for trustworthiness (e.g. has the destination been visited before? If so, it is considered trustworthy) before being allowed to pass or be blocked.</p> <p>Now we know that even though each device does not have its own public IP address, we can still communicate with the external world through our routers using NAT. However, how do we determine what our public IP address is?</p> <p>We can use <strong>STUN</strong> (Session Traversal Utilities for NAT) to help with this.</p> <p>STUN servers are cheap and easy to maintain, and they are mostly public servers that respond to us with our public IP address, port, and connectivity status. We will package this data and send it to our Peer as an ICE candidate, who will do the same (Interactive Connectivity Establishment is a protocol used to transfer data provided by the STUN server in a standardized way). This process allows us to locate each other and connect directly.</p> <p>However, STUN servers can sometimes fail. This is because some NAT methods, such as symmetrical NAT, block direct connections after exchanging information about each other.</p> <p>For example:</p> <p>A has a full-cone NAT while B has a symmetrical NAT. They exchange their information and attempt to connect directly browser-to-browser, as this is what video calls are about - direct connection without delays. A's router, which uses full-cone NAT, allows the signal from B to come through, but B's router encounters A's signal for the first time and does not trust it, blocking it from passing through. This is where <strong>TURN</strong> (Traversal Using Relays around NAT) servers come into play.</p> <p>They provide both peers with information about their IP addresses, ports, and other details, and then act as a mediator between them, allowing the signal to pass through them and eliminating the need for trust between the caller's and receiver's endpoints. TURN servers are more expensive and difficult to maintain, and are often privately owned. They are used as a relay if STUN servers fail.</p> <p>We have reached the point where A has determined their public IP address using STUN or TURN and created an object containing this information as an ICE candidate.</p> <p>As mentioned above, ICE is a protocol that is used to standardize the construction of this information. On the other end of the line, B also did the same.</p> <p>What should we do next? Do we need to gather any more information, and if so, how should we send it? Do we need more information in general?</p> <p>Yes, we need to gather information about our media streams, inputs, and outputs, among other things. We collect this information through WebRTC and create an offer containing it, also called an SDP (Session Description Protocol), which is essentially a string of information.</p> <p>How do we exchange this information with each other? That part is left to your imagination. You can use paper, pigeons, or servers. Mostly, servers are chosen. We implement a web socket that fires when Peer A sends his SDP and ICE candidate to the server. This allows Peer B to react to the event and use this information, while also sending his own SDP. The same process happens with Peer A, as the web socket informs him about Peer B firing an event and he can respond to it.</p> <p>The following code examples represent some of the concepts explained in the article. For example, getting a local video stream and rendering it in an HTML element.</p> <p>In the following code snippet, we write a function called <code>requestMediaDevices</code>, which accesses the <code>navigator</code> object (The Navigator interface represents the state and identity of the user agent). Then, in the <code>mediaDevices</code> object, which contains information about user media devices, we call a method. This method prompts the user for permission to use a media input, which produces a <code>MediaStream</code> with tracks containing the requested types of media.</p> <pre> <code class="language-javascript">private async requestMediaDevices(): Promise&lt;void&gt; { try { this.localMediaStream = await navigator.mediaDevices.getUserMedia(); } catch (error: any) { alert(`An error occured while getting user media ${error.name}`); } }</code></pre> <p>In order to attach a video stream to an HTML element, you need to map through the tracks of the <code>localMediaStream</code> and enable them. These tracks are video and audio. The <code>localVideoStream</code> is just a reference to the <code>&lt;video&gt;</code> HTML element and you bind this flowing stream of media to this element's <code>srcObject</code>.</p> <pre> <code class="language-javascript">this.localMediaStream.getTracks().forEach(track =&gt; { track.enabled = true; }); this.localVideoStream.nativeElement.srcObject = this.localMediaStream;</code></pre> <p>Now we can skim through the call process: </p> <pre> <code class="language-javascript">async startCall(): Promise&lt;void&gt; { this.peerConnection = new RTCPeerConnection(RTCConfiguration); // in RTC configuration we have information about websocket that we are using, stun servers // Here you should write handlers for onicecandidate (retirieve ice candidates and send them to websocket) // Also handlers for oniceconnectionstatechange, onsignalingstatechange and ontrack - to properly handle call failures, retries and etc. //adding local video stream into the mediaStream of the offer this.localMediaStream.getTracks().forEach( track =&gt; this.peerConnection.addTrack(track, this.localVideoStream) ); try { const offer: RTCSessionDescriptionInit = await this.peerConnection.createOffer(); //setting local sdp offer into the localdescription of peerconnection await this.peerConnection.setLocalDescription(offer); this.RTCconnectionWebsocket.sendMessage({type: 'offer', data: offer}); } catch (err: any) { //handle error here } }</code></pre> <p>Now some moments from handling Peer A’s offer from Peer B’s side:</p> <pre> <code class="language-javascript">private handlePeerOffer(offer: RTCSessionDescriptionInit): void { // An sdp from A, which is an offer is a remote description for B and visce versa this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer)) .then(() =&gt; { // add media stream to local video html element reference srcObject just like in Peer A this.localVideoStream.nativeElement.srcObject = this.localMediaStream; // add media tracks to remote connection just like in Peer A's case this.localMediaStream.getTracks().forEach( track =&gt; this.peerConnection.addTrack(track, this.localVideoStream) ); }).then(() =&gt; { // Build SDP for Peer B which in peerConnection would be an answer to the offer return this.peerConnection.createAnswer(); }).then((answer) =&gt; { // Created answer would be B's SDP which should be set as his local description return this.peerConnection.setLocalDescription(answer); }).then(() =&gt; { this.RTCconnectionWebsocket.sendMessage({type: 'peerAnswer', data: this.peerConnection.localDescription}); }).catch(//catch error here); }</code></pre> <p>As the code snippet shows, B sets A's offer as a remote description while creating its own answer and setting it as a local description of his own. Then, it alerts the web socket that the answer was set so that the further event handlers which depend on the web socket getting this type of message can act upon it.</p> <p>Hopefully, this roundup can shed some light on the process of real-time video communication and contribute to enhancing the low-level knowledge of building such applications.</p> </div> <div> <div><a href="/topic/webrtc" class="e-tag-logo-tag"> WebRTC </a> </div> <div><a href="/topic/javascript" class="e-tag-logo-tag"> <img src="/sites/default/files/2021-02/javascript.svg" alt="JavaScript" loading="lazy" width="16" height="16" /> JavaScript </a> </div> <div><a href="/topic/computer-networking" class="e-tag-logo-tag"> Computer Networking </a> </div> <div><a href="/topic/nat" class="e-tag-logo-tag"> NAT </a> </div> <div><a href="/topic/stun" class="e-tag-logo-tag"> STUN </a> </div> <div><a href="/topic/turn" class="e-tag-logo-tag"> TURN </a> </div> </div> <div> <img src="/sites/default/files/styles/blog_full/public/2023-01/webrtc.png?itok=t2TnPoXH" width="1360" height="714" alt="" loading="lazy" typeof="foaf:Image" /> </div> <div id="field-language-display"><div class="js-form-item form-item js-form-type-item form-item- js-form-item-"> <label>Language</label> English </div> </div> Fri, 27 Jan 2023 09:51:38 +0000 Giorgi Beruashvili 83 at https://omedia.dev Unleashing the Power of SVG Templates in Angular https://omedia.dev/blog/unleashing-power-svg-templates-angular <span>Unleashing the Power of SVG Templates in Angular</span> <span><span lang="" about="/user/57" typeof="schema:Person" property="schema:name" datatype="">Besarion Satno</span></span> <span>Fri, 01/20/2023 - 16:34</span> <div class="f-body om-text-content"> <p>As a developer, you are probably familiar with the importance of creating high-quality and visually appealing graphics for your web applications. One of the best ways to achieve this is by using SVG. SVG is a powerful and versatile format that allows you to create and display vector graphics on the web. Since SVG is using XML to describe the graphics, generating dynamic SVGs can be quite useful and simple to implement.</p> <p>Apparently, in Angular, we can use <code>.svg</code> templates for components instead of the <code>.html</code> files we are accustomed to. This opens the door to using Angular's syntactic sugar, like property binding and event binding, with SVGs to create dynamic and interactive graphic content.</p> <p>In this blog post, we will take a closer look at how and why to use SVG templates in Angular. Using SVG templates in Angular allows you to create dynamic, responsive graphics that can change based on dynamic data or user interactions. This makes it a great choice for creating complex graphics, visualizations, and animations for your web applications.</p> <p>To use SVG as an Angular template, first, you need an Angular project. Let’s create one:</p> <pre> <code class="language-bash">ng new svg-demo</code></pre> <p>Now delete everything from <code>app.component.html</code> and create a new component:</p> <pre> <code class="language-bash">ng g c svg-demo</code></pre> <p>I usually manually change the <code>.html</code> suffix to <code>.svg</code>, delete its contents, and update the <code>.ts</code> file with the new <code>templateUrl</code> in the <code>@Component</code> decorator.</p> <pre> <code class="language-javascript">import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-svg-demo', templateUrl: './svg-demo.component.svg', styleUrls: ['./svg-demo.component.scss'], }) export class SvgDemoComponent implements OnInit { constructor() {} ngOnInit(): void {} }</code></pre> <p>CLI takes care of many things, such as adding component declarations to the <code>@NgModule</code> decorator, and we can freely use it in the <code>app.component.html</code> with the <code>&lt;app-svg-demo&gt;&lt;/app-svg-demo&gt;</code> tag.</p> <p>In <code>svg-demo.component.svg</code> I’ll paste the SVG code for this isometric cube which I got from <a href="https://draw.io/" target="_blank">draw.io</a></p> <img alt="Cube" data-entity-type="file" data-entity-uuid="c9769ad9-64eb-4139-a1fb-f4323cfb8c4e" src="/sites/default/files/inline-images/cube_0.png" class="align-center" width="1200" height="602" loading="lazy" /> <p>Here is the contents of <code>cube.svg</code>:</p> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)"/&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" fill="none" stroke="rgb(0, 0, 0)"/&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>To break this code down, the surrounding <code>&lt;svg&gt;</code> tag contains one <code>&lt;g&gt;</code> tag, which stands for "group" and groups two <code>&lt;path&gt;</code> tags. Those two paths represent these two parts of our shape:</p> <img alt="Paths comprising the cube" data-entity-type="file" data-entity-uuid="36634f36-41b3-40ed-86b2-6e1a68e9f7d0" src="/sites/default/files/inline-images/cube-parts.png" class="align-center" width="1200" height="562" loading="lazy" /> <p>Now, let's make this markup more Angular<em>-y</em> and decide what we want to do with it.</p> <p>One of the most basic things we can do in Angular Components is to bind <code>.html</code> and <code>.ts</code> files. In our case, we can bind values from <code>.ts</code> to the values in <code>.svg</code>. Let's consider the colors of those shapes and make the outline of the same color.</p> <p>Below, we have just an array of <code>colors</code>, a <code>strokeColor</code> property, and a function that returns a randomly chosen color from the array.</p> <pre> <code class="language-javascript">import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-svg-demo', templateUrl: './svg-demo.component.svg', styleUrls: ['./svg-demo.component.scss'], }) export class SvgDemoComponent implements OnInit { colors: string[] = [ 'red', 'orange', 'yellow', 'green', 'blue', 'purple', 'black', ]; strokeColor: string | undefined; constructor() {} getRandomColor() { const randomNumber = Math.floor(Math.random() * this.colors.length); return this.colors[randomNumber]; } ngOnInit(): void { this.strokeColor = this.getRandomColor(); } }</code></pre> <p>I want to bind the randomly chosen color to the stroke attribute of those two shapes, as we want to change both of them.</p> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" fill="rgb(255, 255, 255)" [attr.stroke]="strokeColor" /&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" fill="none" [attr.stroke]="strokeColor" /&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>Now the color of the stroke changes on every refresh. Let's also change the fill color of the first <code>&lt;path&gt;</code> tag. I will modify the <code>.ts</code> file for my purposes:</p> <pre> <code class="language-javascript">strokeColor: string | undefined; // add new property fillColor: string | undefined; ngOnInit(): void { this.strokeColor = this.getRandomColor(); // assign random color to this as well this.fillColor = this.getRandomColor(); }</code></pre> <p>And I'll update the SVG template as well:</p> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>Here are the results of multiple reloads, all with different (random) colors:</p> <img alt="Color variants" data-entity-type="file" data-entity-uuid="3aef3718-7507-4126-abc2-96d15d8f1576" src="/sites/default/files/inline-images/variants.png" class="align-center" width="3000" height="1571" loading="lazy" /> <p>Let's change the behavior and make the colors change on mouse click, instead of relying on the refresh button. We can use Angular's event binding syntax for this. I will modify the files once again:</p> <pre> <code class="language-javascript">changeColors() { this.strokeColor = this.getRandomColor(); this.fillColor = this.getRandomColor(); } ngOnInit(): void { this.changeColors(); }</code></pre> <pre> <code class="language-xml">&lt;svg width="91px" height="101px" viewBox="-0.5 -0.5 91 101" style="background-color: rgb(255, 255, 255);"&gt; &lt;g (click)="changeColors()"&gt; &lt;path d="M 45 0 L 90 21.61 L 90 78.39 L 45 100 L 0 78.39 L 0 21.61 Z" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;path d="M 0 21.61 L 45 43.21 L 90 21.61 M 45 43.21 L 45 100" [attr.fill]="fillColor" [attr.stroke]="strokeColor" /&gt; &lt;/g&gt; &lt;/svg&gt;</code></pre> <p>That's basically it. If you want to move the shape, you can use the <code>x</code> and <code>y</code> attributes for each element, or for all of them at once with the parent tag.</p> <h4>Few things to keep in mind:</h4> <p><strong>1. </strong>When Angular renders the SVG Component in the app-root, it is usually rendered inside a &lt;div&gt;-like element. And if you want to nest multiple SVG components, you should change the component selector and use the directive's syntax like this:</p> <pre> <code class="language-javascript">@Component({ selector: '[app-svg-demo]', ... });</code></pre> <pre> <code class="language-xml">&lt;svg app-svg-demo&gt;&lt;/svg&gt;</code></pre> <p><strong>2. </strong>We need to use <code>[attr.X]</code> syntax to not get the following error:</p> <pre> <code>error NG8002: Can't bind to 'fill' since it isn't a known property of ':svg:path'.</code></pre> <p>According to Angular documentation:</p> <blockquote> <p>This error arises when attempting to bind to a property that does not exist. Any property binding must correspond to either:</p> <ul> <li>A native property on the HTML element, or</li> <li>An <code>@Input()</code> property of a component or directive applied to the element.</li> </ul> </blockquote> <p>So by using <code>[attr.X]</code> syntax, we make sure that Angular knows that we’re trying to bind the value to the native property on the HTML (in our case SVG) element.</p> <p><strong>3. </strong>We can as easily use the <code>@Input</code> and <code>@Output</code> data / event binding between a parent and child components.</p> <p><strong>4. </strong>We can also use structural directives <code>*ngIf</code> / <code>*ngFor</code> with the SVG elements (don’t forget to add <code>CommonModule</code> to the <code>AppModule</code> imports).</p> <p><strong>5. </strong>SVG paths and shapes follow their order. The next element will be drawn on top of the previous one.</p> </div> <div> <div><a href="/topic/angular" class="e-tag-logo-tag"> <img src="/sites/default/files/2021-02/angular.svg" alt="Angular" loading="lazy" width="16" height="16" /> Angular </a> </div> <div><a href="/topic/svg" class="e-tag-logo-tag"> SVG </a> </div> <div><a href="/topic/javascript" class="e-tag-logo-tag"> <img src="/sites/default/files/2021-02/javascript.svg" alt="JavaScript" loading="lazy" width="16" height="16" /> JavaScript </a> </div> <div><a href="/topic/typescript" class="e-tag-logo-tag"> TypeScript </a> </div> </div> <div> <img src="/sites/default/files/styles/blog_full/public/2023-01/angular-svg.png?itok=EkRMKKje" width="1360" height="714" alt="" loading="lazy" typeof="foaf:Image" /> </div> <div id="field-language-display"><div class="js-form-item form-item js-form-type-item form-item- js-form-item-"> <label>Language</label> English </div> </div> Fri, 20 Jan 2023 12:34:45 +0000 Besarion Satno 82 at https://omedia.dev Getting started with Prettier — Modern JavaScript Tooling (Part 1) https://omedia.dev/blog/getting-started-prettier-modern-javascript-tooling-part-1 <span>Getting started with Prettier — Modern JavaScript Tooling (Part 1)</span> <span><span lang="" about="/user/5" typeof="schema:Person" property="schema:name" datatype="">Giorgi Kapanadze</span></span> <span>Mon, 07/27/2020 - 18:36</span> <div class="f-body om-text-content"> <p>JavaScript has grown into an enormous ecosystem for the past couple of years, which makes learning this language exciting and challenging at the same time. The vast array of available tools and frameworks might be a roadblock for some junior developers who just can’t figure out where to start and what to use.</p> <p>The general rule of thumb is to get the basics of the language first and move to nuts and bolts down the road. For sure, this is a valid approach. In my experience, though, some tools can be essential even for the absolute beginners, and knowing how to set up and use those can make the learning curve smoother.</p> <p>So in this series of posts, I’ll be covering a couple of must-have (or, must-take-a-look) tooling of the modern JS ecosystem, as well as tips on how to use and integrate them in our projects.</p> <hr /> <p>Before we begin, let’s set up our environment. We won’t need much for the tools discussed in this post — just <a href="https://nodejs.org/en/download/">get the latest Node.js</a> on your machine, if you don’t have it installed already. Node is compatible with all three operating systems, so you’re free to use your OS of choice for the development machine.</p> <h2>Coding style</h2> <p>Imagine that you’re working on a project with a couple of fellow developers. All those guys and gals will have their preferred coding styles — from indentation preferences to bracket placements, line wrapping, and commenting methods. The resulting code of a team without a strict coding style guidelines will be a mess to read and hard to follow through.</p> <p>This is why the coding style and standards have to be agreed upon at the start of the project and followed by all members without any exceptions. But we’re all humans and we have this beautiful (but sometimes — annoying) thing called <a href="https://en.wikipedia.org/wiki/Muscle_memory">muscle memory</a>. Muscle memory in our fingers helps us perform routine tasks without even thinking (like creating a code block for IF statement) but also prevents us from “switching” between those behaviors quickly. This is why it’s pretty hard to switch your coding mannerisms from project to project and it’s unreliable to hope that the coding styles will be followed by every team member all the time.</p> <p><a href="https://prettier.io/"><strong>Prettier</strong></a> solves this problem by automating the code formatting.</p> <p>Let’s create a new directory and initialize NPM in it. Open terminal, jump to the project folder and run <code>npm init -y</code> command. This will create the following <code>project.json</code> file for us:</p> <img alt="snippet" data-entity-type="file" data-entity-uuid="6ec4b20b-3872-46c6-b131-eee2e362bb5d" src="/sites/default/files/inline-images/01.png" class="align-center" width="1430" height="844" loading="lazy" /> <p>Now we can add <strong>Prettier</strong> to our project. Use this command to install it in our project:</p> <pre> <code class="language-bash">npm install --save-dev prettier</code></pre> <p>The<code> --save-dev</code><strong> </strong>flag tells NPM to install this package only for the development environment (we won’t need it elsewhere since the code will be formatted during the development.) After the command is finished, we’ll have the <code>node_modules</code> folder in our project directory and the <code>package.json</code> will be updated as well (you’ll see the <code>devDependencies</code> group since the Prettier was installed for the dev environment only):</p> <img alt="snippet" data-entity-type="file" data-entity-uuid="50cd15d4-069c-429b-bb18-6b8731b0ad42" src="/sites/default/files/inline-images/02.png" class="align-center" width="1430" height="558" loading="lazy" /> <p>Now let’s make some mess for the Prettier to do its job. Create a new <code>.js</code> file and type some code without any formatting consistencies. Something like this:</p> <img alt="snippet" data-entity-type="file" data-entity-uuid="ec98a95e-5fa9-48cb-8a69-3959c3056c04" src="/sites/default/files/inline-images/03.png" class="align-center" width="1946" height="802" loading="lazy" /> <p>This code has several formatting issues: for one, the strings are written with both types of quotes — single and double; the <code>function</code> block is indented with 4 spaces, while the if-statement block is using 2 spaces for the same job; some lines are too long; and so on.</p> <p>Let’s put Prettier to work. Create a new script called <code><strong>format</strong></code> that runs the Prettier<strong> </strong>for us. Our <code>package.json</code> should look like this:</p> <pre> <code class="language-json">{ "name": "modern-js", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "format": "prettier --write" }, "keywords": [], "license": "ISC", "devDependencies": { "prettier": "^1.19.1" } } </code></pre> <p>The scripts described in <code>package.json</code> lets us run complex commands from the <code>node_modules/.bin</code> directory in a shortcut-like manner. Let’s take a look inside this folder:</p> <img alt="snippet" data-entity-type="file" data-entity-uuid="bad0db59-5bbe-4cf0-8901-796779e6310a" src="/sites/default/files/inline-images/04.png" class="align-center" width="1946" height="401" loading="lazy" /> <p><code>prettier</code> is a regular executable JavaScript file. Scripts in <code>package.json</code> help us create a “shortcut” for a command like this:</p> <pre> <code class="language-bash">./node_modules/.bin/prettier --write src/index.js</code></pre> <p>To run the same command with NPM script, we can just run this command from our project root directory (the same directory that holds <code>package.json</code>):</p> <pre> <code class="language-bash">npm run format src/index.js</code></pre> <p>The last part of both commands — <code>src/index.js</code> — tells Prettier which file to format. After the command is finished, our messy file will look like this:</p> <img alt="snippet" data-entity-type="file" data-entity-uuid="f98ec105-5e22-47ab-86e9-79f2dee488a6" src="/sites/default/files/inline-images/05.png" class="align-center" width="1673" height="1073" loading="lazy" /> <p>I think we can agree that this code is, indeed, <em>prettier</em>. But most importantly, it’s easier to read or quickly scan to understand its structure. The code is more consistent, line indentations are the same, <code>if-else</code> blocks look much better and chained methods are broken into lines.</p> <p>But this style might not be the same as what your team has agreed on. For example, the team might consider 2-space indentation as their default, or to use single quotes for strings, instead of double ones. This is why Prettier rules are configurable and you can change the defaults with the <code>.prettierrc</code> file. Let’s create it in our project’s root directory and put this piece of JSON in it:</p> <pre> <code class="language-json">{ "tabWidth": 4, "semi": true, "singleQuote": true }</code></pre> <p>With this we’re letting Prettier know that we prefer 4 spaces for indentation (<code>tabWidth</code>); also that we want semicolons to be compulsory (<code>semi</code>) and to force single-quotes for writing string literals instead of double ones (<code>singleQuote</code>).</p> <p>After running the format script again, we’ll get the <code>src/index.js</code> file formatted with our configured rules:</p> <img alt="snippet" data-entity-type="file" data-entity-uuid="2c2fb21a-5735-4bf7-a72b-4a0f80f381e0" src="/sites/default/files/inline-images/06.png" class="align-center" width="1673" height="1073" loading="lazy" /> <p>The configuration options we used are just a small part of the set <a href="https://prettier.io/docs/en/options.html">prettier configuration options</a> you can use to match its behavior with your project style guidelines.</p> <p>Before we wrap up, let’s extend our <code>format</code> command a bit and make it more flexible to use. Most probably, we’ll be needing the formatting guidelines forced on all files in our project, not just on a single one. Instead of running the script on all files one by one, we can specify a file name pattern for Prettier that will pick up all <code>.js</code> files inside our <code>src/</code> directory and it’s child directories as well.</p> <p>Open the <code>package.json</code> file and add the following script to the <strong>scripts</strong> object:</p> <pre> <code class="language-json">"format:all": "prettier --write \"src/**/*.js\""</code></pre> <p>To see the new script in action, create some more <code>.js</code> files in the <code>src/</code> directory; or create some directories in <code>src/</code> and put the <code>.js</code> files inside those.</p> <p>Now, when you run <code>npm run format:all</code> all your <code>.js</code> files in <code>src/</code> directory will be (recursively) picked up by Prettier and re-formatted with rules specified in the <code>.prettierrc</code> configuration file.</p> <hr /> <p>In this small introduction to <strong>Prettier</strong> you’ve learned how to use it in terminal and with NPM scripts. It’s very easy to integrate this tool in your project workflow, configure it, and forget about code formatting altogether. You can never think about the formatting guidelines of a specific project, just focus on your code and Prettier will do the rest.</p> <p>Prettier can also be integrated into your favorite code editor (for example, see extensions for <a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">VS Code</a>, <a href="https://atom.io/packages/prettier-atom">Atom</a>, or <a href="https://github.com/jonlabelle/SublimeJsPrettier">Sublime</a> editors). With integration like this, the code formatting will be performed every time you save the file, which makes using this tool even more comfortable and seamless.</p> </div> <div> <div><a href="/topic/prettier" class="e-tag-logo-tag"> Prettier </a> </div> <div><a href="/topic/javascript" class="e-tag-logo-tag"> <img src="/sites/default/files/2021-02/javascript.svg" alt="JavaScript" loading="lazy" width="16" height="16" /> JavaScript </a> </div> <div><a href="/topic/tooling" class="e-tag-logo-tag"> Tooling </a> </div> <div><a href="/topic/code-style" class="e-tag-logo-tag"> Code Style </a> </div> </div> <div> <img src="/sites/default/files/styles/blog_full/public/2021-03/prettier.png?itok=-ymTm9HG" width="1360" height="714" alt="" loading="lazy" typeof="foaf:Image" /> </div> <div id="field-language-display"><div class="js-form-item form-item js-form-type-item form-item- js-form-item-"> <label>Language</label> English </div> </div> Mon, 27 Jul 2020 14:36:23 +0000 Giorgi Kapanadze 22 at https://omedia.dev