<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: собачья будка</title>
    <description>The latest articles on Forem by собачья будка (@scumdograscalhouse).</description>
    <link>https://forem.com/scumdograscalhouse</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F346225%2F73dc8476-9049-400c-a559-1927ea79d3e4.jpeg</url>
      <title>Forem: собачья будка</title>
      <link>https://forem.com/scumdograscalhouse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/scumdograscalhouse"/>
    <language>en</language>
    <item>
      <title>плавающее оглавление на solidjs</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Fri, 24 Apr 2026 06:41:32 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/plavaiushchieie-oghlavlieniie-na-solidjs-9ce</link>
      <guid>https://forem.com/scumdograscalhouse/plavaiushchieie-oghlavlieniie-na-solidjs-9ce</guid>
      <description>&lt;p&gt;иногда плавающее оглавление кажется довольно простой штукой: берёшь заголовки, рисуешь список, добавляешь якоря — готово.&lt;/p&gt;

&lt;p&gt;в этой статье — разбор table of contents, который я собрал на solidjs: от поиска заголовков до sticky/fixed поведения и адаптивного ui.&lt;/p&gt;




&lt;h2&gt;
  
  
  контекст
&lt;/h2&gt;

&lt;p&gt;задача :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;собрать список заголовков статьи&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;сделать быстрые переходы по клику&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;подсвечивать текущий раздел при скролле&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;поддерживать мобильный и десктопный режим&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;уметь скрываться и появляться&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  сбор заголовков из документа
&lt;/h2&gt;

&lt;p&gt;вся система начинается с обхода dom и поиска заголовков внутри контейнера статьи.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateHeadings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentSelector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1, h2, h3, h4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;setHeadings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;setAreHeadingsLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;по сути это “снимок структуры документа”.&lt;/p&gt;




&lt;h2&gt;
  
  
  debounce пересборки оглавления
&lt;/h2&gt;

&lt;p&gt;чтобы не пересобирать список слишком часто:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;debouncedUpdateHeadings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateHeadings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;это особенно важно, если контент может динамически изменяться.&lt;/p&gt;




&lt;h2&gt;
  
  
  определение активного заголовка
&lt;/h2&gt;

&lt;p&gt;самая “живая” часть компонента — отслеживание скролла.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isInViewport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_HEADER_OFFSET&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;и дальше поиск активного элемента:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateActiveHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isInViewport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nf"&gt;setActiveHeaderIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  переход к заголовку
&lt;/h2&gt;

&lt;p&gt;при клике происходит ручной scroll с компенсацией fixed header’а:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scrollToHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="nx"&gt;DEFAULT_HEADER_OFFSET&lt;/span&gt;

  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrollTo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smooth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  состояние компонента
&lt;/h2&gt;

&lt;p&gt;оглавление держит сразу несколько слоёв состояния:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHeadings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createSignal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeHeaderIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActiveHeaderIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsVisible&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;areHeadingsLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setAreHeadingsLoaded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDocumentReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsDocumentReady&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  подписка на скролл
&lt;/h2&gt;

&lt;p&gt;основная реактивность строится через window scroll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setIsDocumentReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;debouncedUpdateHeadings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateActiveHeader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;onCleanup&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateActiveHeader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  реакция на изменение статьи
&lt;/h2&gt;

&lt;p&gt;если меняется тело статьи — пересобираем структуру:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDocumentReady&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;debouncedUpdateHeadings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  рендер списка
&lt;/h2&gt;

&lt;p&gt;основной ui — это список кнопок, привязанных к заголовкам.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TableOfContentsHeadingsList&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;For&lt;/span&gt; &lt;span class="na"&gt;each&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
          &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TableOfContentsHeadingsItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TableOfContentsHeadingsItemH3&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;H3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TableOfContentsHeadingsItemH4&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;H4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;activeHeaderIndex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;innerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;scrollToHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  визуальная иерархия заголовков
&lt;/h2&gt;

&lt;p&gt;уровни заголовков просто сдвигаются визуально:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.TableOfContentsHeadingsItemH3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.TableOfContentsHeadingsItemH4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  активный пункт
&lt;/h2&gt;

&lt;p&gt;подсветка текущего раздела минимальная, но важная:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.TableOfContentsHeadingsItem.active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;700&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  sticky поведение на десктопе
&lt;/h2&gt;

&lt;p&gt;на больших экранах оглавление становится sticky-блоком:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.TableOfContentsContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@include&lt;/span&gt; &lt;span class="err"&gt;media-breakpoint-up(xl)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sticky&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100vh&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;120px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  mobile режим (fixed bottom panel)
&lt;/h2&gt;

&lt;p&gt;на мобильных это уже не sidebar, а выезжающая панель:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.TableOfContentsFixedWrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@include&lt;/span&gt; &lt;span class="err"&gt;media-breakpoint-down(xl)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  toggle видимости
&lt;/h2&gt;

&lt;p&gt;оглавление можно скрывать и показывать:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggleIsVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setIsVisible&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  итог
&lt;/h2&gt;

&lt;p&gt;в итоге это не просто оглавление.&lt;/p&gt;

&lt;p&gt;это:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;парсинг DOM структуры статьи&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;debounce пересборки контента&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scroll tracking с throttle&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;вычисление активного раздела&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sticky + fixed адаптивный layout&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UI, который живёт вместе со скроллом&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;и самое интересное — такие компоненты всегда выглядят простыми в начале, но постепенно превращаются в полноценный слой навигации поверх документа, который “понимает” структуру текста и помогает по нему двигаться.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/discours/discoursio-webapp/tree/dev/src/components/_shared/TableOfContents" rel="noopener noreferrer"&gt;source code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>tutorial</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>image cropper на solidjs</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Fri, 24 Apr 2026 06:28:49 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/image-cropper-na-solidjs-39nc</link>
      <guid>https://forem.com/scumdograscalhouse/image-cropper-na-solidjs-39nc</guid>
      <description>&lt;p&gt;иногда кажется, что кроп изображения — это просто ui-компонент: выделил область, обрезал, сохранил.&lt;/p&gt;

&lt;p&gt;но когда ты начинаешь делать это руками через canvas, drag’n’drop и пересчёт координат с учётом scale — внезапно получается маленький графический редактор.&lt;/p&gt;

&lt;p&gt;в этой статье — разбор image cropper, который я собрал на solidjs: от canvas-рендера до экспорта файла.&lt;/p&gt;




&lt;h2&gt;
  
  
  контекст
&lt;/h2&gt;

&lt;p&gt;задача была простой на словах:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;загрузить изображение&lt;/li&gt;
&lt;li&gt;дать пользователю выбрать область&lt;/li&gt;
&lt;li&gt;добавить зум&lt;/li&gt;
&lt;li&gt;сохранить результат как файл&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;но почти сразу стало понятно, что это не dom-задача, а работа с пикселями.&lt;/p&gt;




&lt;h2&gt;
  
  
  сам компонент
&lt;/h2&gt;

&lt;p&gt;это основной компонент кроппера. он держит всё состояние внутри: canvas, drag, scale, загрузку и экспорт.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UploadFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@solid-primitives/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clsx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onCleanup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Show&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;solid-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLocalize&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/context/localize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ImageCropper.module.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CropperProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UploadFile&lt;/span&gt;
  &lt;span class="nx"&gt;onSave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
  &lt;span class="nx"&gt;onDecline&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ImageCropper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CropperProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLCanvasElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;imageRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLImageElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLDivElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cropData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCropData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDragging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsDragging&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dragStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDragStart&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setImageLoaded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setScale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// логика ниже&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  стили: это уже не просто ui
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.cropperContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--black-50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--comment-radius-md&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;идея простая:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;центрированная рабочая зона&lt;/li&gt;
&lt;li&gt;ощущение инструмента, а не формы&lt;/li&gt;
&lt;li&gt;минимум отвлекающего ui&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  canvas как рабочая поверхность
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.cropperCanvas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--shadow-color-medium&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;canvas визуально отделён — как холст в редакторе.&lt;/p&gt;




&lt;h2&gt;
  
  
  grab vs dragging
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.cropperCanvas&lt;/span&gt; &lt;span class="nt"&gt;canvas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grab&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.cropperCanvas&lt;/span&gt; &lt;span class="nt"&gt;canvas&lt;/span&gt;&lt;span class="nc"&gt;.dragging&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grabbing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  zoom контролы
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.zoomControl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  адаптивность
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.cropperContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.cropperCanvas&lt;/span&gt; &lt;span class="nt"&gt;canvas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100vw&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;max-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  как работает рендер
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drawImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;imageRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageRef&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;displayWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;displayHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offsetX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;displayWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offsetY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;displayHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offsetX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offsetY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;displayWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;displayHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rgba(0, 0, 0, 0.5)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cropData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strokeStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#fff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strokeRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  drag логика
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cropData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setIsDragging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setDragStart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  перемещение области
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseMove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isDragging&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dragStart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;cropData&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;drag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;cropData&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;drag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nf"&gt;setCropData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;cropData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newY&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  экспорт кропа
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cropImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cropData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageRef&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;displayWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;displayHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offsetX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;displayWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offsetY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;displayHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;offsetX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;offsetY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;currentScale&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cropCanvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;cropCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
  &lt;span class="nx"&gt;cropCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cropCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;300&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cropCanvas&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  сохранение файла
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;croppedCanvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cropImage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;croppedCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;`cropped-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  итог
&lt;/h2&gt;

&lt;p&gt;в итоге это не просто кроппер.&lt;/p&gt;

&lt;p&gt;это:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canvas-рендеринг&lt;/li&gt;
&lt;li&gt;ручная система координат&lt;/li&gt;
&lt;li&gt;drag &amp;amp; zoom&lt;/li&gt;
&lt;li&gt;экспорт в файл&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;и самое интересное — чем дальше ты заходишь, тем меньше это похоже на ui-компонент и тем больше на мини-редактор изображений внутри браузера.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/discours/discoursio-webapp/tree/dev/src/components/_shared/ImageCropper" rel="noopener noreferrer"&gt;source code&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>кастомный аудиоплеер на solidjs вокруг нативного audio api</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Fri, 24 Apr 2026 06:00:40 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/kak-ia-sdielal-audioplieier-v-discourse-38c3</link>
      <guid>https://forem.com/scumdograscalhouse/kak-ia-sdielal-audioplieier-v-discourse-38c3</guid>
      <description>&lt;p&gt;как я добавил в проект аудиоплеер с плейлистом, контролами и базовым управлением звуком. это не отдельный виджет, а часть страницы с текстом, поэтому он связан с контентом (заголовки, шаринг и т.д.).&lt;/p&gt;




&lt;h2&gt;
  
  
  данные и состояние
&lt;/h2&gt;

&lt;p&gt;на вход приходит массив медиа (&lt;code&gt;media&lt;/code&gt;), который я сначала нормализую:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;добавляю &lt;code&gt;id&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;добавляю флаги &lt;code&gt;isCurrent&lt;/code&gt; и &lt;code&gt;isPlaying&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prepareMedia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isCurrent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;дальше всё состояние плеера живёт в этом массиве &lt;code&gt;tracks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;текущий трек определяется через &lt;code&gt;isCurrent&lt;/code&gt;. если его нет — автоматически выбирается первый.&lt;/p&gt;

&lt;p&gt;тут есть небольшой сайд-эффект — если текущего трека нет, он выставляется прямо внутри геттера. не идеально, но в этом месте это упростило код.&lt;/p&gt;




&lt;h2&gt;
  
  
  переключение треков и play/pause
&lt;/h2&gt;

&lt;p&gt;при клике на трек или кнопку play я полностью пересобираю массив:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTracks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isCurrent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;только один трек может быть текущим&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;только один может играть&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;не стал выносить это в отдельный store — здесь проще пересобрать массив, чем поддерживать несколько источников состояния.&lt;/p&gt;

&lt;p&gt;при переключении next/prev:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;считается индекс текущего трека&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;выбирается следующий или предыдущий (с зацикливанием)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  синхронизация с &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;есть отдельный &lt;code&gt;audioRef&lt;/code&gt;, который реально воспроизводит звук.&lt;/p&gt;

&lt;p&gt;через &lt;code&gt;createEffect&lt;/code&gt; я слежу за текущим треком:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nf"&gt;getCurrentTrack&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCurrentTrack&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;
  &lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;если трек поменялся — обновляется &lt;code&gt;src&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  воспроизведение
&lt;/h2&gt;

&lt;p&gt;при play:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;если контекст звука “спит” — резюмлю его&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;вызываю &lt;code&gt;audioRef.play()&lt;/code&gt; или &lt;code&gt;pause()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCurrentTrack&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  прогресс и время
&lt;/h2&gt;

&lt;p&gt;прогресс обновляется вручную:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;progressFilledRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;беру &lt;code&gt;currentTime&lt;/code&gt; из &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;делю на &lt;code&gt;duration&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;пишу в width&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;прогресс обновляется напрямую через style — это проще, чем городить отдельную реактивную прослойку.&lt;/p&gt;

&lt;p&gt;время форматируется через &lt;code&gt;Date&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  перемотка (scrub)
&lt;/h2&gt;

&lt;p&gt;при клике по прогресс-бару:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetX&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;progressRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;при зажатой кнопке мыши — обновляется на &lt;code&gt;mousemove&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;используется &lt;code&gt;offsetX&lt;/code&gt;, что не всегда идеально, но для этого кейса оказалось достаточно.&lt;/p&gt;




&lt;h2&gt;
  
  
  обработка событий audio
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;onTimeUpdate&lt;/code&gt; → обновляю прогресс и текущее время&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;onLoadedMetadata&lt;/code&gt; → получаю длительность&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;onEnded&lt;/code&gt; → сбрасываю прогресс и время&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  громкость через web audio api
&lt;/h2&gt;

&lt;p&gt;я не использовал просто &lt;code&gt;audio.volume&lt;/code&gt;, а сделал через &lt;code&gt;AudioContext&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;createMediaElementSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;gainNode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;audioContext&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;и дальше:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;gainNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;volumeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;громкость управляется через &lt;code&gt;input[type=range]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;громкость сделал через web audio api — немного оверкилл, но зато полный контроль.&lt;/p&gt;

&lt;p&gt;с range-инпутом пришлось немного повозиться из-за разных браузеров.&lt;/p&gt;




&lt;h2&gt;
  
  
  header (контролы)
&lt;/h2&gt;

&lt;p&gt;в &lt;code&gt;PlayerHeader&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;play / pause&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;next / prev&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;кнопка громкости&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;громкость открывается как popover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isVolumeBarOpened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsVolumeBarOpened&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;и закрывается через кастомный хук &lt;code&gt;useOutsideClickHandler&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  плейлист
&lt;/h2&gt;

&lt;p&gt;в &lt;code&gt;PlayerPlaylist&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;список треков (&lt;code&gt;&amp;lt;For each={tracks}&amp;gt;&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;кнопка play у каждого трека&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;отображение текущего состояния (play/pause иконка)&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getCurrentTrack&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;getCurrentTrack&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;список не просто отображает данные — он тоже участвует в управлении плеером.&lt;/p&gt;




&lt;h2&gt;
  
  
  шаринг
&lt;/h2&gt;

&lt;p&gt;у каждого трека есть кнопка шаринга:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SharePopup&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;shareUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{...}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;используется заголовок трека&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;описание берётся из текста статьи&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ссылка формируется от &lt;code&gt;articleSlug&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  стили
&lt;/h2&gt;

&lt;p&gt;в css:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;flex layout для header и контролов&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;адаптив через breakpoint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;кастомный progress bar (через &lt;code&gt;border&lt;/code&gt; и &lt;code&gt;::after&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;кастомный range input для громкости (webkit / moz / ms)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;прогресс-бар и ползунок громкости полностью переопределены, дефолтные стили не используются.&lt;/p&gt;




&lt;p&gt;в итоге это не отдельный плеер, а часть страницы — с текстом, шарингом и списком треков, которые живут вместе.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/discours/discoursio-webapp/commit/a18b6b9e6d3fe6985a49f4671b81d3f3fa36fefb" rel="noopener noreferrer"&gt;source code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>showdev</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>как сконвертировать пачку изображений в webp</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 30 Mar 2022 07:14:26 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/kak-skonviertirovat-pachku-izobrazhienii-v-webp-4noo</link>
      <guid>https://forem.com/scumdograscalhouse/kak-skonviertirovat-pachku-izobrazhienii-v-webp-4noo</guid>
      <description>&lt;p&gt;для этого понадобится либа &lt;a href="https://developers.google.com/speed/webp/docs/cwebp" rel="noopener noreferrer"&gt;cwebp&lt;/a&gt;, которая принимает на вход степень сжатия (1..100), имя входного и выходного файлов, и простой баш цикл.&lt;br&gt;
соберем все картинки в одну директорию и перейдем в нее, после чего опишем скрипт:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for file in *
do
cwebp -q 100 "$file" -o "${file%.png}.webp"
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>как наложить блюр в фигме</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 30 Mar 2022 07:12:41 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/kak-nalozhit-bliur-v-fighmie-4kg5</link>
      <guid>https://forem.com/scumdograscalhouse/kak-nalozhit-bliur-v-fighmie-4kg5</guid>
      <description>&lt;p&gt;для формы блюра используем любую фигуру (&lt;code&gt;R&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh39pe1vkgdijxek7p0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh39pe1vkgdijxek7p0x.png" alt=" " width="242" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;и выделим необходимое для замутнения место:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy593my7ip2eh1zzno8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy593my7ip2eh1zzno8m.png" alt=" " width="800" height="789"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;изменим цвет заливки на белый с полупрозрачностью в 1%:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5qxl8h3s9pzbs5dzhl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5qxl8h3s9pzbs5dzhl8.png" alt=" " width="508" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;и добавим эффект &lt;code&gt;background blur&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjdjwrjofp867nsh8izn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjdjwrjofp867nsh8izn.png" alt=" " width="502" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;клик на иконку позволяет настраивать степень блюра. well done!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie1qb891f801ol0spu5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie1qb891f801ol0spu5a.png" alt=" " width="800" height="787"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>генерируем xlsx из rss фида</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Sun, 27 Mar 2022 06:22:06 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/ghienieriruiem-xlsx-iz-rss-fida-4fpo</link>
      <guid>https://forem.com/scumdograscalhouse/ghienieriruiem-xlsx-iz-rss-fida-4fpo</guid>
      <description>&lt;p&gt;нагулял задачу генерации таблиц из rss фида, парсеры выходят на арену. план такой: из терминала кормим скрипт набором аргументов с линком к rss фиду и его настройками, добавляем прогресс бары для отзывчивости, на выход отдаем сгенерированную таблицу.&lt;/p&gt;

&lt;p&gt;сперва набросаем пачку зависимостей:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add rss-parser
yarn add --dev exceljs moment posthtml progress request request-promise yargs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;за дело! импортирую переменные и определяю парсер:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ExcelJS = require('exceljs')
const Parser = require('rss-parser')
const posthtml = require('posthtml')
const rp = require('request-promise')
const moment = require('moment')
const ProgressBar = require('progress')
const yargs = require("yargs")

const parser = new Parser()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;описываю обязательные и не очень аргументы, определяю входную функцию и на ходу рассказываю о прогрессе в терминал:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const options = yargs
    .usage(`Usage: -f &amp;lt;rss uri&amp;gt;`)
    .option('f', {
        alias: 'feed',
        describe: 'RSS feed uri',
        type: 'string',
        demandOption: true
    })
    .option('a', {
        alias: 'amount',
        describe: 'Needed RSS feed posts amount',
        type: 'string'
    })
    .option('n', {
        alias: 'outputFileName',
        describe: 'XLS output file name',
        type: 'string'
    })
    .option('o', {
        alias: 'cellOptions',
        describe: 'Sheet cell additional options',
        type: 'array'
    })
    .argv

process.stdout.write(`great options, bruh, let's start already!\n`)

entry(options.feed, options.amount, options.outputFileName, options.cellOptions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;главная функция будет принимать на вход обязательный линк на фид и необязательные количество постов, имя таблицы на выходе и набор кастомных настроек для ячеек. назначаю нужные столбцы и их ключи:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let entry = async (rssFeed, amount = 5, outputFileName = 'result', cellOptions = []) =&amp;gt; {
    process.stdout.write(`parsing your rss feed...\n`)
    let feed = await parser.parseURL(rssFeed)

    process.stdout.write(`creating excel workbook...\n`)
    const workbook = new ExcelJS.Workbook()
    const worksheet = workbook.addWorksheet(outputFileName)
    worksheet.columns = [{
            header: 'text',
            key: 'col_text'
        },
        {
            header: 'url',
            key: 'col_url'
        },
        {
            header: 'images',
            key: 'col_images'
        },
        {
            header: 'time',
            key: 'col_time'
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;приступаю к генерации новых строк и добавляю тикающий прогресс бар:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process.stdout.write(`generating posts from rss feed...\n`)
let generatedRows = await generatePostsMetaFromFeed(feed, amount)
let generatedRowsBar = new ProgressBar('[:bar] :current/:total table rows generated\n', {
    incomplete: ' ',
    complete: '#',
    total: generatedRows.length
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;функция &lt;code&gt;generatePostsMetaFromFeed&lt;/code&gt; займется пирсингом элементов фида и генерацией набора с нужными таблице полями:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let convertFeedToPosts = feed =&amp;gt; [...feed.items.map(item =&amp;gt; item.link)] // для пагинации по страницам фида понадобятся линки

let generatePostsMetaFromFeed = async (feed, amount) =&amp;gt; {
    let res = []

    let posts = []
    let feedLink = feed.link

    if (amount &amp;gt; 10) {
        process.stdout.write(`wow, so much posts? taking care of it...\n`)
        let pages = Math.round(amount / 10) // пагинация для доступа к последующим страницам фида
        let pagesLoadingBar = new ProgressBar('[:bar] :current/:total processed\n', {
            incomplete: ' ',
            complete: '#',
            total: pages
        })

        posts.push(...convertFeedToPosts(feed))

        process.stdout.write(`loading needed pages...\n`)
        for (let i = 2; i &amp;lt;= pages; i++) {
            await rp(encodeURI(`${feedLink}?feed=rss&amp;amp;paged=${i}`))
                .then(async rssPage =&amp;gt; {
                    let parsedRSSFeed = await parser.parseString(rssPage)
                    let isLastPage = i === pages

                    if (isLastPage) {
                        let modItems = parsedRSSFeed.items.filter((_, index) =&amp;gt; index &amp;lt; amount % 10)

                        posts.push(...convertFeedToPosts({
                            items: modItems
                        }))
                    } else {
                        posts.push(...convertFeedToPosts(parsedRSSFeed))
                    }

                    pagesLoadingBar.tick()
                })
                .catch(err =&amp;gt; {
                    console.error('huh, rss pagination failed', err.code)
                })
        }
    } else {
        process.stdout.write(`not a lot of posts, gonna be quick!\n`)
        posts.push(...convertFeedToPosts({
            items: feed.items.slice(0, amount)
        }))
    }

    process.stdout.write(`time to generate some text for our table!\n`)
    let postsHandlingBar = new ProgressBar('[:bar] :current/:total posts handled\n', {
        incomplete: ' ',
        complete: '#',
        total: posts.length
    })

    for (let i = 0; i &amp;lt; posts.length; i++) {
        let postLink = posts[i]
        let title, description, image

        await rp(postLink)
            .then(html =&amp;gt; {
                process.stdout.write(`wuush, working on it...\n`)
                posthtml().use(tree =&amp;gt; { // парсим дерево и только нужные таблице значения нод
                    tree.match({
                        tag: 'title'
                    }, node =&amp;gt; {
                        title = node.content[0]
                    })
                    tree.match({
                        attrs: {
                            name: 'description'
                        },
                        tag: 'meta'
                    }, node =&amp;gt; {
                        description = node.attrs.content
                    })
                    tree.match({
                        attrs: {
                            property: 'og:image'
                        },
                        tag: 'meta'
                    }, node =&amp;gt; {
                        image = node.attrs.content
                    })
                }).process(html)

                postsHandlingBar.tick()
            })
            .catch(err =&amp;gt; {
                console.error('huh, post parsing failed', err)
            })

        res.push({
            title,
            description,
            image,
            link: postLink
        })
    }

    return res
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;строки сгенерированы, пора вернуться во входную &lt;code&gt;entry&lt;/code&gt; функцию и прикрутить их к инстансу &lt;code&gt;worksheet&lt;/code&gt;, используя метод &lt;code&gt;addRow&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process.stdout.write(`making some rows for your sheet...\n`)
for (let i = 0; i &amp;lt; generatedRows.length; i++) {
    let {
        title,
        description,
        image,
        link
    } = generatedRows[i]
    let columnText = `${title}\n\n${description}\n\n${link}`

    if (cellOptions.length) {
        cellOptions.forEach(cOption =&amp;gt; {
            if (cOption === 'noImage') {
                image = ''
            }
            if (cOption === 'noOGCard') {
                link = ''
            }
        })
    }

    worksheet.addRow({
        col_text: columnText,
        col_url: link,
        col_images: image,
        col_time: moment().add(i, 'days').format('DD/MM/YYYY hh:mm').toString()
    })

    generatedRowsBar.tick()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;lift off! теперь можно отдавать таблицу:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process.stdout.write(`creating your ${outputFileName} file...\n`)
await workbook.xlsx.writeFile(`${outputFileName}.xlsx`)
    .then(() =&amp;gt; {
        process.stdout.write(`${outputFileName} created allright!\n`)
    })
    .catch((err) =&amp;gt; {
        process.stdout.write('huh, creating error: ', err)
    })

process.stdout.write(`all done, love!\n`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;таблица в кармане, profit!&lt;/p&gt;

&lt;p&gt;исходный код: &lt;a href="https://github.com/arkatriymfalnaya/xlsx-from-rss-generator" rel="noopener noreferrer"&gt;https://github.com/arkatriymfalnaya/xlsx-from-rss-generator&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>очередной слайдер, которого никто не просил</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Sat, 26 Mar 2022 06:17:52 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/ochieriednoi-vielosipied-na-moiem-balkonie-c77</link>
      <guid>https://forem.com/scumdograscalhouse/ochieriednoi-vielosipied-na-moiem-balkonie-c77</guid>
      <description>&lt;p&gt;натыкаясь на вакансии от разных контор, я периодически берусь за тестовые задания, чтобы поразвлечься. в этот раз выбор пал на, кто бы сомневался, очередной слайдер. ну и пусть.&lt;/p&gt;

&lt;p&gt;собираем проект, пара библиотек нам пригодится:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init
yarn add path express pug path
yarn add --dev nodemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;сразу добавим &lt;code&gt;dev&lt;/code&gt; скрипт в &lt;code&gt;package.json&lt;/code&gt;, который будет стучаться к нашему сервер-скрипту:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "dev": "nodemon src/server.js"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;создадим папку &lt;code&gt;src&lt;/code&gt; и файл &lt;code&gt;server.js&lt;/code&gt;, в котором опишем наш сервер и движок для pug шаблонов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express')
const path = require('path')
const fs = require('fs')

const app = express()
const port = '8000'

app.listen(port, () =&amp;gt; {
    console.log(`Listening on http://localhost:${port}`)
})

app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'pug')
app.use(express.static(path.join(__dirname, 'public')))

app.get('/', (_, res) =&amp;gt; {
    try {
        let paths = fs.readdirSync(path.join(__dirname + '/public/images/')) // там будут храниться наши картинки для слайд-шоу
        let combinedPaths = paths.map(p =&amp;gt; `images/${p}`)

        res.render('index', {
            imagesPaths: combinedPaths
        })
    } catch (err) {
        throw err
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;самое время набросать разметку. одна центрирующая обертка, одна секция самого слайдера, контролы и сами слайды, которые отдаются серверным скриптом:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doctype html
html(lang='en')
  head
    meta(charset='utf-8')
    link(rel='stylesheet' href='styles/index.css')
    meta(name='viewport', content='width=device-width, initial-scale=1, shrink-to-fit=no')
  body
    main.wrapper
        section.slider
            each src in imagesPaths
                article.slider__item.slider__item--appear.slide
                    figure
                        img(src=src alt='pic')
            section.slider__controls
                button
                button
    script(type='module' src='scripts/index.js')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;переходим к самому вкусному - к интерактивности, которую возьмет на себя скрипт &lt;code&gt;scripts/index.js&lt;/code&gt;. сперва обезопасим себя и дождемся загрузки DOM дерева:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('DOMContentLoaded', () =&amp;gt; {
  …
}, false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;вытащим из дерева нужные ноды, объявим переменные для интервала и учёта текущего индекса слайда:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const slidesList = document.querySelectorAll('.slider__item')
const controlsList = document.querySelector('.slider__controls').children
const leftControl = controlsList[0]
const rightControl = controlsList[1] // больше двух детей не предполагается

let interval = null
let currentIndex = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;в конце скрипта инициализируем события и рендеринг самого слайдера:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('keydown', e =&amp;gt; {
    if (e.key === 'ArrowLeft') moveSlider('left') // переключение левой стрелкой
    if (e.key === 'ArrowRight') moveSlider('right') // переключение правой стрелкой
    if (e.key === ' ' &amp;amp;&amp;amp; e.target.nodeName !== 'BUTTON') {
        if (interval) {
            stopSliding() // остановка на текущем слайде по нажатию на пробел
        } else {
            proceedSliding() // продолжение слайд-шоу, если была остановка
        }
    }
})
leftControl.addEventListener('click', () =&amp;gt; moveSlider('left'), false)
rightControl.addEventListener('click', () =&amp;gt; moveSlider('right'), false)

initSlider(slidesList)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;столько функций еще не написано! надо исправлять:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initSlider = slides =&amp;gt; {
    if (currentIndex &amp;gt; slides.length) currentIndex = 1
    if (currentIndex &amp;lt; 1) currentIndex = slides.length

    updateSlider(slidesList)
}

const moveSlider = to =&amp;gt; {
    if (to === 'right') updateCurrentIndex('right')
    else updateCurrentIndex('left')

    initSlider(slidesList)
}

…

const stopSliding = () =&amp;gt; {
    clearInterval(interval)
    interval = null // очищаем интервал, чтобы остановить слайд-шоу

    slidesList[currentIndex - 1].classList.add('slide--stopped')
}

const proceedSliding = () =&amp;gt; {
    interval = setInterval(() =&amp;gt; {
        updateCurrentIndex('right')
        initSlider(slidesList)
    }, 3000)
    slidesList[currentIndex - 1].classList.remove('slide--stopped')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;многие участки дублируются, потому выносим их в отдельные хендлеры:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const updateSlider = slides =&amp;gt; {
    clearInterval(interval)

    for (let i = 0; i &amp;lt; slides.length; i++) {
        slides[i].classList.add('slider__item--inactive') // стили класса будут удалять слайд из разметки
        slides[i].classList.remove('slide--stopped')
    }

    slides[currentIndex - 1].classList.remove('slider__item--inactive')

    interval = setInterval(() =&amp;gt; { // переопределяем интервал, ведь слайд-шоу не просто так шоу
        updateCurrentIndex('right')
        initSlider(slidesList)
    }, 3000)
}

…

const updateCurrentIndex = to =&amp;gt; {
    if (to === 'right') currentIndex++
    else currentIndex--
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ой, почти все! добавим стили и анимации для появления слайдов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* {
    box-sizing: border-box;
}

body, section, figure, button {
    padding: 0;
    margin: 0;
}

.wrapper {
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, 1);
}

.slider {
    position: relative;
    width: 100%;
    height: 540px;
}

.slider__views-counter {
    position: absolute;
    top: -28px;
    left: 0;
    margin: 0;
    margin-left: 14px;
    font-family: 'Helvetica';
    font-size: 20px;
    line-height: 24px;
    color: white;
}

.slider__item {
    display: flex;
    height: 100%;
}

.slider__item--inactive {
    display: none;
}

.slider__item--appear {
    animation-name: slide-appearing;
    animation-duration: 1.5s;
}

.slide {
    align-items: center;
}

.slide--stopped {
    outline: 5px auto Highlight;
}

.slide figure {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

.slide figure img {
    max-width: 100%;
    width: auto;
    max-height: 100%;
    height: auto;
}

.slider__controls {
    position: absolute;
    z-index: 2;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    margin: auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}

.slider__controls button {
    position: relative;
    width: 14%;
    max-width: 80px;
    height: 40%;
    border: none;
    background: transparent;
    cursor: pointer;
    border-radius: 8px;
    background-color: rgba(255, 255, 255, .14);
}

.slider__controls button:hover {
    background-color: rgba(255, 255, 255, .18);
}

.slider__controls button:active {
    box-shadow: 0px 0px 22px rgba(0, 0, 0, 0.2);
}

.slider__controls button::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    margin: auto;
    width: 60%;
    height: 60%;
    background-image: url(/icons/arrow.svg);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    filter: invert();
}

.slider__controls button:nth-child(2)::after {
    transform: rotate(180deg);
}

@keyframes slide-appearing {
    from {
        opacity: .2;
    }
    to {
        opacity: 1;
    }
}

@media screen and (min-width: 1020px) {
    .slider {
      width: 80%;
      max-width: 1020px;
    }

    .slider__views-counter {
        margin-left: 0;
    }

    .slider__controls button {
        max-width: 40px;
    }
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;пора бы глянуть, чего уже там нарисовалось то. запустим &lt;code&gt;yarn dev&lt;/code&gt; и перейдем по &lt;code&gt;http://localhost:8000&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F516kolihee5dharkglw6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F516kolihee5dharkglw6.gif" alt=" " width="80" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;а что, дизайн вышел покурить? ну и пусть, ведь важнее, что внутри.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>как я избавлялся от экстремизма в блоге</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Thu, 24 Mar 2022 13:28:44 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/kak-ia-ot-ekstriemizma-izbavlialsia-3f3d</link>
      <guid>https://forem.com/scumdograscalhouse/kak-ia-ot-ekstriemizma-izbavlialsia-3f3d</guid>
      <description>&lt;p&gt;по мотивам недавних судебных решений блог моей компании о соцсетях и вокруг попадал под длящееся правонарушение. неопределенность по поводу нужных действий от других компаний и отсутствие решения суда сбивало с толку, потому, прежде чем скрывать все статьи с упоминаниями продуктов компании M**a, было решено подстраховаться иноагентскими методами. &lt;/p&gt;

&lt;p&gt;первым вариантом был скрипт, которому можно скормить статьи и регуляркой добавить сноски всем упоминаниям экстремистских продуктов. с этой идеи начал, этой идеей и закончил.&lt;/p&gt;

&lt;p&gt;wordpress хранит статьи в базе, доступ из админки по вкладке &lt;code&gt;записи&lt;/code&gt;. встроенные инструменты позволяют экспортировать и импортировать контент из базы в удобном &lt;code&gt;xml&lt;/code&gt; формате, осталось разобраться со структурой. для парсинга разогнал библиотеку &lt;code&gt;fast-xml-parser&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const data = fs.readFileSync(path.join(__dirname, fileName), 'utf8')
const parser = new XMLParser()

let jObj = parser.parse(data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;рассмотрев дерево, выделил нужные мне ветки: &lt;code&gt;wp:postmeta&lt;/code&gt; и &lt;code&gt;content:encoded&lt;/code&gt;, дело за малым. пишем простую регулярку, выискивающую подстроку, которая не начинается с символа &lt;code&gt;[&lt;/code&gt; (используется для markdown тегов), имеет одно вхождение из набора названий запрещенных продуктов &lt;code&gt;(название1|название2|название3)&lt;/code&gt; и любое окончание для русской вариации &lt;code&gt;[а-яА-Я]*&lt;/code&gt;. для туллтипов использовался wordpress плагин &lt;a href="https://getshortcodes.com/docs/" rel="noopener noreferrer"&gt;Shortcodes Ultimate&lt;/a&gt;. поскольку реплейсить придется для нескольких веток, выносим эту исторую в отдельную функцию и обрабатываем корнер кейсы:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let replaceWithToolbar = str =&amp;gt; str.replace(/[^\[|\/](meta|instagram|facebook|инстаграм|мета|фейсбук)[а-яА-Я]*/gi, (subStr, _, subStrIndex) =&amp;gt; {
  let nextSymbol = str[subStrIndex + subStr.length]

  if(str[subStrIndex - 3] + str[subStrIndex - 2] + str[subStrIndex - 1] + str[subStrIndex] === 'www.') return subStr

  let space = '&amp;lt;code style="letter-spacing: -7px;"&amp;gt; &amp;lt;/code&amp;gt;'

  let start = subStr[0]
  let end = nextSymbol === ' ' ? space : ''
  let updatedStr = subStr.substring(1)

  let tooltipText =
    subStr.includes('нстагра') || subStr.includes('ейсб') || subStr.includes('nstagr') || subStr.includes('aceboo')
      ? 'Продукт принадлежит организации, признанной экстремистской на территории Российской Федерации.'
      : 'Организация признана экстремистской на территории Российской Федерации.'

  return `${start}${space}[su_tooltip text="${tooltipText}" text_align="center"]${updatedStr}[/su_tooltip]${end}`
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;остается только грязно обновить исходные ветки на получившиеся:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let metasArray = jObj.rss.channel.item['wp:postmeta']
let newMetasArray = metasArray.map(m =&amp;gt; {
  if(m['wp:meta_key'] === '_crb_description' || m['wp:meta_key'] === '_crb_short_description') {
    let newMeta = replaceWithToolbar(m['wp:meta_value'])
    m['wp:meta_value'] = newMeta
  }

  return m
})
jObj.rss.channel.item['wp:postmeta'] = newMetasArray

let content = jObj.rss.channel.item['content:encoded']
let newContent = replaceWithToolbar(content)
jObj.rss.channel.item['content:encoded'] = newContent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;после чего билдим дерево в новый xml и пишем в файл:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const builder = new XMLBuilder({ processEntities:false })
const xmlContent = builder.build(jObj)
fs.writeFileSync(path.join(__dirname, `output_${fileName}`), xmlContent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ну, вроде подстраховались!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnieiz68ro34yviib8bwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnieiz68ro34yviib8bwe.png" alt=" " width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;исходный код: &lt;a href="https://github.com/arkatriymfalnaya/avoid-extremism" rel="noopener noreferrer"&gt;https://github.com/arkatriymfalnaya/avoid-extremism&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>architectural styles and the design of network-based software architectures. representational state transfer [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:43 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/architectural-styles-and-the-design-of-network-based-software-architectures-representational-state-transfer-3jde</link>
      <guid>https://forem.com/scumdograscalhouse/architectural-styles-and-the-design-of-network-based-software-architectures-representational-state-transfer-3jde</guid>
      <description>&lt;p&gt;Вольный перевод главы диссертации Roy Thomas Fielding "&lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm" rel="noopener noreferrer"&gt;Representational State Transfer (REST)&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Representational State Transfer (REST)
&lt;/h1&gt;

&lt;p&gt;Эта глава представляет и конкретизирует Representational State Transfer (REST) - архитектурный стиль для распределенных гипермедийных систем, описывает принципы разработки программного обеспечения, следуя REST, а также описывает ограничения взаимодействия для сохранения этих принципов, в то же время сопоставляя их с ограничениями других архитектурных стилей. REST - это гибридный стиль, полученный из описанных в &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm" rel="noopener noreferrer"&gt;третьей лаве&lt;/a&gt; архитектурных стилей и определяющий единый интерфейс соединителя коннектора в сочетании с дополнительными ограниченияеми. Структура архитектуры ПО из &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/software_arch.htm" rel="noopener noreferrer"&gt;первой главы&lt;/a&gt; используется для определения архитектурных элементов REST изучения примеров просессов, коннекторов и представлений данных прототипных архитектур.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.1 Процесс выведения REST
&lt;/h2&gt;

&lt;p&gt;Обоснование проектирования, лежащее в основе веб архитектуры, может быть описано архитектурым стилем, который состоит из набора применяемых к элементам ограничений. Изучая влияние каждого ограничения по мере го добавления к развивающемуся стилю, мы может определять свойства, индуцированные ограничениями веба. Затем для формирования нового архитектурного стиля, который лучше отражает свойства современной веб архитектуры, могут быть применены дополнительные ограничения. Эта секция предоставляет общий обзор REST путём рассмотрения процесса выведения его как архитектурного стиля. Конкретные ограничения, из которых состоит REST, будут описаны в последующих разделах  .&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.1 Начиная с Нулевого Стиля
&lt;/h3&gt;

&lt;p&gt;Как для построек, так и для программного обеспечения существует два общих взгляда на процесс архитектурного проектирования. Первый: проектировщик начинает с пустого состояния, рисовальной или чертежной доски и, пока потребности предполагаемой системы не будут удовлетворены, выстраивает архитектуру из уже знакомых компонентов. Второй: проектировщик отталкивается от потребностей системы без ограничений, после чего постепенно определяет и применяет ограничения к её элементам, чтобы разграничить область проектирования и позволить силам, влияющим на поведение системы, течь естественным образом, в гармонии с системой. Первый взгляд подразумевает креативное и неограниченное видение, второй - сдержанность и понимание контекста нужной системы. REST был разработан с помощью последнего. Фигуры с 5-1 по 5-8 графически описывают, как примененные ограничения разграничивают процесс представления архитектуры в качестве инкрементального набора ограничений.&lt;/p&gt;

&lt;p&gt;Нулевой стиль (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_1" rel="noopener noreferrer"&gt;5-1&lt;/a&gt;) - это просто пустой набор ограничений. С точки зрения архитектуры, нулевой стиль описывает систему, в которой четкая граница между компонентами не установлена. Это начальная точка для нашего описания REST.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fnull_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fnull_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/null_style.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.2 Клиент-сервер
&lt;/h3&gt;

&lt;p&gt;Первыми добавленными к нашему гибридному стилю ограничениями будут ограничения клиент-серверного архитектурного стиля (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_2" rel="noopener noreferrer"&gt;5-2&lt;/a&gt;),  описанные в  &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_1" rel="noopener noreferrer"&gt;Секции 3.4.1&lt;/a&gt;. Принцип, лежащий в основе клиент-серверных ограничений, заключается в разделении интересов. Разделением интересов пользовательского интерфейса от интересов хранилища данных, мы улучшаем портативность пользовательского интерфейса на несколько платформ и улучшаем масшабируемость, упрощая серверные компоненты. Для веба наиболее важным, пожалуй, является то, что разделение позволяет компонентам развиваться независимо, тем самым поддерживая требования расширяемости интернета для нескольких организационных доменов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fclient_server_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fclient_server_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/client_server_style.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.3 Отсутствие состояния (Stateless)
&lt;/h3&gt;

&lt;p&gt;Следующее ограничение клиент-серверного взаимодействия: коммуникация должна не иметь состояния по своей природе, следуя стилю client-stateless-server (CSS) из &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_3" rel="noopener noreferrer"&gt;Секции 3.4.3&lt;/a&gt; (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_3" rel="noopener noreferrer"&gt;5-3&lt;/a&gt;), что подразумевает, что каждый запрос от клиента к серверу должен содержать всю необходимую для его понимания информацию и не может использовать хранящийся на сервере контекст. Потому состояние сессии полностью хранится на клиенте.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fstateless_cs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fstateless_cs.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/stateless_cs.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Это ограничение индуцирует свойства видимости, надежности и расширяемости. Видимость улучшается за счет того, что системе мониторинга не нужно выходить за пределы данных запроса, чтобы определить его полную природу. Надежность улучшается, потому что облегчается задача по восстановлению после частичных сбоев. Расширяемость улучшается, потому что отсуствие необходимости хранения состояния между запросами позволяет серверному компоненту быстрее освобождать ресурсы и еще больше упрощает реализацию, потому что серверу не приходится управлять использованием ресурсов между запросами.&lt;/p&gt;

&lt;p&gt;Как и большинство архитектурных решений, ограничение в виде отсутствия состояния является компромиссом проектирования. Недостатком может быть то, что этот способ может снизить производительность сети из-за увеличения количества повторяющихся данных (per-interaction overhead), отправляемых в сериях запросов, поскольку эти данные нельзя оставлять на сервере в общем контексте. К тому же, хранение состояния приложения на стороне клиента уменьшает возможность контроля за поведением приложения со стороны сервера, поскольку приложение становится зависимым от правильности реализации семантики для нескольких версияй клиента.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.4 Кэш
&lt;/h3&gt;

&lt;p&gt;Для того, чтобы улучшить эффективность сети, мы добавляем ограничения кэша для формирования стиля client-cache-stateless-server из &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_4" rel="noopener noreferrer"&gt;секции 3.4.4&lt;/a&gt; (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_4" rel="noopener noreferrer"&gt;5-4&lt;/a&gt;). Ограничения кэша требуют, чтобы данные внутри ответа на запрос были явно или неявно отмечены, как кэшируемые или некэшируемые. Если ответ кэшируемый - клиентскому кэшу дается право переиспользовать данные ответа для последующих эквивалентных запросов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fccss_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fccss_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/ccss_style.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Преимущество от добавления ограничения кэша в том, что появляется возможность частично или полностью исключить некоторые взаимодействия за счет уменьшения средней задержки их серии, тем самым улучшая эффективность, расширяемость и воспринимаемую пользователем производительность. Компромисс, однако, заключается в том, что кэш может снизить надежность, если устаревшие данные в кэше значительно отличаются от данных, полученных при отправке запроса на непосредственно сервер.&lt;/p&gt;

&lt;p&gt;Ранняя веб архитектура, как показано на изображении &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_5" rel="noopener noreferrer"&gt;5-5&lt;/a&gt;, определялась client-cache-stateless-server набором ограничений. Таким образом, для обмена статическими документами через интернет представленное для веб архитектуры до 1994 обоснование проектирования было сфокусировано на клиент-серверном взаимодействии без состояния. Протоколы для взаимного взаимодействия имели рудиментарную поддержку для кэшей без общего доступа, но не ограничивали интерфейс согласованным набором семантики для всех ресурсов. Вместо того, в вебе использовалась общая библиотека клиент-серверной реализации (CERN libwww) для обеспечения согласованности между веб приложениями.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fearly_web_arch.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Fearly_web_arch.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/early_web_arch.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Разработчики веб реализаций уже превзошли ранние методы проектирования. Кроме статических документов, запросы могли идентифицировать сервисы, которые динамически генерировали ответы, вроде image-maps [Кевин Хьюз] и server-side scripts [Роб МакКул]. Также была начата работа над промежуточными компонентами в виде прокси-серверов и общих кэшей, но для надежной связи те нуждались в расширених протоколов. Следующие разделы описывают ограничения, которые были добавлены к архитектурному стилю веба, для указания расширений, формирующих современную веб архитектуру.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.5 Унифицированный интерфейс
&lt;/h3&gt;

&lt;p&gt;Главной отличительной чертой REST стиля от остальных является, акцент на единый интерфейс между компонентами (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_6" rel="noopener noreferrer"&gt;5-6&lt;/a&gt;). Применение такого принципа к интерфейсу компонент улучшает видимость взаимодействий и позволяет упростить архитектуру системы в целом. Реализации отделены от сервисов, которые они предоставляют, что способствует их независимому развитию. Компромисс в том, что унифицированный интерфейс снижает эффективность, поскольку информацию передается в стандартизированном виде, а не в виде, определенном под нужды приложения. Интерфейс REST был спроектирован для эффективной передачи крупного количества гипермедиа данных, частого случая в вебе, но не оптимален для других видов архитектурного взаимодействия.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Funiform_ccss.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Funiform_ccss.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/uniform_ccss.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Чтобы достчиь унифицированного интерфейса, на поведение компонентов должны быть наложены некоторые архитектурные ограничения. REST определяется четырьмя интерфейсными ограничениями: идентификация ресурсов; управление ресурсами через представления; самоописывающиеся сообщения; и гипермедиа, как двигатель состояния приложения. Эти ограничения будут обсуждены в &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_2" rel="noopener noreferrer"&gt;секции 5.2&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.6 Многоуровневая система
&lt;/h3&gt;

&lt;p&gt;Для последующего улучшения поведения и требований расширяемости интернета, мы добавили ограчения уровневой системы (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_7" rel="noopener noreferrer"&gt;5-7&lt;/a&gt;). Как описано в &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_2" rel="noopener noreferrer"&gt;секции 3.4.2&lt;/a&gt;, уровневый системный стиль позволяет архитектуре состоять их иерархичных слоев путем особого поведения компонентов - каждый компонент не может "смотреть" за пределы уровня, с которым взаимодействует. Ограничивая знание о системе до одного уровня, мы обеспечиваем подложке незавимость и ограничиваем сложность системы в целом. Уровни могут быть использованы, чтобы оборачивать legacy сервисы и огорождать  от них новые, упрощая компоненты за счет перемещения редкоиспользуемых функций в общий промежуточный уровень. Такие уровни также могут использоваться для улучшения расширяемости системы, обеспечивая балансировку нагрузки служб между несколькими сетями и процессорами.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Flayered_uccss.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Flayered_uccss.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/layered_uccss.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Главным недостатом уровненой системы является то, что они увеличивают расходы и, соответстно, задержку при обработке данных, снижая воспринимаемую пользователем производительность. Для сетевой системы, поддерживающей ограничения кэширования, эти издержки можно компенсировать преимуществами общего кэширования в промежуточном уровне. Размещение общиъ кэшей на границах организационного домена может привести к значительному повышению производительности. Такие уровни также позволяют применять политики безопасности к данным, пересекающим эти границы, как того требуют файрволлы.&lt;/p&gt;

&lt;p&gt;Комбинация многоуровневой системы и ограничений унифицированного интерфейса приводит архитектурные свойства к виду pipe-and-filter (секция &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_2_2" rel="noopener noreferrer"&gt;3.2.2&lt;/a&gt;). Хотя REST взаимодействие является двусторонним, потоки данных большого размера гипермедиа взаимодействия могут обрабатываться как сеть потоков данных с фильтрующими компонентами, выборочно примеяемыми к потокам для преобразования их содержимого. Внутри REST промежуточные компоненты могут активно преобразовывать содержимое сообщений, потому что сообщения самоописывающиеся и их семантика видна на промежуточных уровнях.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.7 Код по требованию
&lt;/h3&gt;

&lt;p&gt;Последним дополнением к нашему набору ограничений будет  стиль code-on-demand из секции &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_5_3" rel="noopener noreferrer"&gt;3.5.3&lt;/a&gt; (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_8" rel="noopener noreferrer"&gt;5-8&lt;/a&gt;). REST позволяет клиенту расширяться функциональности клиента и выполнять код в виде апплетов или скриптов. Это упрощает клиентов, уменьшая количество необходимых для предварительной реализации фич. Благодаря возможности загрузки фич после деплоя, улучшается расширяемость системы. Однако, видимость также уменьшается, потому это ограничение внутри REST является опциональным.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Frest_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Frest_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_style.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Понятие опционального ограничения может показаться оксюмороном. Тем не менее, оно имеет предназначение при проектировании системы, охватывающей несколько организационных границ. Это значит, что, когда опциональное ограничение действует только для некоторой области системы, архитектура получает преимущество. Например, если все клиентское ПО внутри организации поддерживает Java-апплеты, то сервисы в этой организации могут быть сконструированы таким образом, чтобы они получали преимущества расширенной функциональности с помощью загружаемых классов Java. Однако, файрволл организации может не допустить передачу Java-апплетов из внешних источников, и, следовательно, для остальной части Интернета это будет выглядеть так, как будто эти клиенты не поддерживают код по требованию. Опциональное ограничение позволяет нам проетировать архитектуру, которая в общих случаях поддерживает желаемое поведение, но может быть отключена в некоторых контекстах.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.8 Summary
&lt;/h3&gt;

&lt;p&gt;REST состоит из набора рахитектурных ограничений, выбранных из-за присущих им свойст. В то же время, каждое из этих ограничений можно рассматривать отдельно, а описание их с точки зрения происхождения из общих архитектурных стилей облегчает понимание обоснования их выбора. Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_9" rel="noopener noreferrer"&gt;5-9&lt;/a&gt; отображает ответвления ограничений REST в терминах сетевых архитектурных стилей, описанных в главе 3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Frest_derivation.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Frest_derivation.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_derivation.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5.2 Архитектурные элементы REST
&lt;/h2&gt;

&lt;p&gt;Стиль REST является абстракцией архитектурных элементов внутри распределенной гипермедийной системы. REST игнорирует детали реализации компонента и синтаксис протокола для лучшего фокусирования на ролях компонентов, их интерпретации значимых элементов данных, ограничений их взаимодействия с другими компонентами. REST охватывает фундаментальные ограничения компонентов, коннекторов и данных, которые определяют основу веб архитектуры и, таким образом, сущность его поведения как сетевого приложения.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.1 Элементы данных
&lt;/h3&gt;

&lt;p&gt;В отличие от распределенного объектного стиля, где все данные скрываются внутри компонентами обработки, природа и состояние архитектурных элементов данных - ключевой аспект  REST.  Обоснование такого проектирования можно увидеть в природе самих распределенных гипермедиа. Когда выбирается ссылка, информация должна переместиться  из места хранения в место, где она будет использоваться пользователем. В отличие от многих других парадигм распределенной обработки, где возможно и обычно более эффективно перемещать сам «агент обработки» (например, мобильный код, хранимую процедуру, выражение поиска и т. д.) к данным, а не перемещать данные в процессор.&lt;/p&gt;

&lt;p&gt;Распределенная гипермедийная архитектура имееть только три основных свойства: 1) отрисовка данных в месте их хранения и отправка получателю изображений фиксированного формата; 2) инкапсуляция данных с помощью рендер движка и отправка обоих получателю; 3) отправка raw данных получателю вместе с метаданными, описывающими тип данных, чтобы получатель мог выбрать свой собственный движок рендеринга.&lt;/p&gt;

&lt;p&gt;Каждой свойство имеет как преимущества, так и недостатки. Первое свойство, традиционно клиент-серверного стиля, позволяет всей информации о настоящей природе данных оставаться скрытой внутри отправителя, упрощая реализацию клиента и предотвращая неточные суждения о структуре данных. Однако, оно также серьезно ограничивает возможности получателя и накладывает большую часть нагрузки на отправителя, что может привести к проблемам расширяемости. Второе свойство, стиля мобильного объекта, предоставляет сокрытие информации, позволяя особым образом обрабатывать данные с помощью уникального движка рендеринга, но ограничивает возможность получателя определить, чего нужно ожидать от движка рендеринга и может значительно увеличить объем передаваемых данных. Третье свойство позволяет отправителю оставаться простым и расширяемым, минимизируя передаваемые байты, но теряет преимущества сокрытия информации и обязывает отправителя и получателя понимать одинаковые типы данных.&lt;/p&gt;

&lt;p&gt;REST предоставляет гибрид этих трех свойств, фокусируясь на совместном понимании типов данных с метаданными, но ограничивает объем раскрываемых для стандартизированного интерфейса данных. REST компоненты взаимодействую между собой путем передачи представления ресурса в формате, соответсвующем одному из развивающихся наборов стандартных типов данных, отобранных динамически на основе природы самого ресурса и возможностей или требований получателя. Представление будет скрываться за интерфейсом внезавимости от того, находится ли оно в том же формате, что и необработанный источник, или получено из него. Преимущества стиля мобильного объекта достигаются посредством отправки представления, состоящего из инструкций в стандартном формате данных инкапсулированного движка рендеринга (например, как в Java). Потому REST достигает разделения интересов клиент-серверного стиля, не мешая расширяемости системы, и позволяет скрывать информацию через основной интерфейс, чтобы обеспечить инкапсуляцию и эволюцию сервисов, и обеспечивает разнообразный набор функций через загружаемые движки.&lt;/p&gt;

&lt;p&gt;Элементы данных REST обобщены в таблице &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#tab_5_1" rel="noopener noreferrer"&gt;5-1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/fd530e9f1c8a444fb5a02fc431323627" rel="noopener noreferrer"&gt;Untitled&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.1.1 Ресурсы и идентификаторы ресурса
&lt;/h3&gt;

&lt;p&gt;Ключевой асбтракцией информации в REST является &lt;em&gt;ресурс&lt;/em&gt;. Любая информация, которая может быть названа, может являться ресурсом: документ или изображение, временный сервис (например, "погода в Лос Анджелес на сегодня"), коллекция других ресурсов, невиртуальный объект (например, человек) и так далее. Другими словами, любой концепция, которая может быть целью гипертекстовой ссылки автора, должна попадать под определение ресурса. Ресурс - это концептуальное отображение набора сущностей, а не сущности, которая соответствует сопоставлению в любой конкретный момент времени.  &lt;/p&gt;

&lt;p&gt;Точнее, ресурс &lt;em&gt;R&lt;/em&gt; является изменяющейся функцией &lt;em&gt;M*R&lt;/em&gt;(t)&lt;em&gt;, которая за время *t&lt;/em&gt; mapsотображает набор сущностей или значений, который эквивалентны. Значения в наборе могут быть &lt;em&gt;представлениями ресурсов&lt;/em&gt; и/или &lt;em&gt;идентификаторами ресурсов&lt;/em&gt;. Ресурс может отображаться на пустой набор, который позволяет создавать ссылки на концепцию перед любой реализацией этой концепции - понятие, которое было чуждо большинству гипертекстовых систем до появления веба. Некоторые ресурсы являются статическими в том смысле, что при рассмотрении в любое время после их создания они всегда соттветствуют одному и тому же набору значений. Другие имеют высокую степень расхождений в занчениях с тчечением времени. Единственное, что требуется для статинчости ресурса - семантика, поскольку именно она отличает один ресурс от другого.&lt;/p&gt;

&lt;p&gt;Например, "предпочтительной версией авторов" академической статьи является отображение, значение которого меняется со временем, тогда как "статьи, опубликованные в материалах конференции X" статичны. Это два разных ресурса, даже если они оба отображают одно и то же значение в определенный момент времени. Различие необходимо для того, чтобы оба ресурса можно было идентифицировать и ссылаться независимо. Аналогичным примером из разработки будет являться раздельный идентификатор файла исходного кода с управлением версиями при образении к "последней версии", "версии 1.2.7" или "версии, включенной в релиз Orange".&lt;/p&gt;

&lt;p&gt;Это абстрактное определение ресурса включает ключевые особенности веб архитектуры. Первое: оно обеспечивает универсальность, охватывая множество источников информации без искусственного их различения по типу или реализации. Второе: оно допускает позднюю привязку ссылки к представлению, позволяя осуществлять согласование содержимого на основе характеристик запроса. И, наконец, позволяет автору ссылаться на концепцию, а не на какое-то единичное представление этой концепции, тем самым устраняя необходимость менять все существующие ссылки при изменении представления (при условии, что автор использовал правильный идентификатор).&lt;/p&gt;

&lt;p&gt;REST использует идентификатор ерсурса для идентификации конкретного ресурса, участвующего во взаимодействии между компонентами. REST коннекторы предоставляют общий интерфейс для доступа к набору значений ресурса и манипулирования им независимо от того, как определена функция членства или от типа программного обеспечения, которое обрабатывает запрос. Инструмент, который назначил идентификатор ресурса, позволяющий ссылаться на ресурс, отвечает за поддержание семантической достоверности отображения с течением времени (т.е. гарантирует, что функция членства не изменяется).&lt;br&gt;
Традиционные гипертекстовые системы, которые обычно действуют в закрытых или локальных окружениях, используеют уникальные идентификаторы узов или документов, которые изменяются сразу после изменения информации, полагаясь на серверы ссылок, чтобы поддреживать отдельно ссылки от контента. Поскольку централизованные серверы ссылок - это анафема для огромного масштаба и многопрофильных сетевых требований, вместо этого REST полагается на то, что автор выбирает идентификатор ресурса, который наилучшим образом соответствует природе идентифицируемой концепции. Естественно, качество идентификатора часто пропорционально сумме денег, потраченной на сохранение его валидности, что приводит к разрыву ссылок, когда эфемерная (или плохо поддерживаемая) информация перемещается или исчезает со временем.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.1.2 Представления
&lt;/h3&gt;

&lt;p&gt;REST компоненты выполняют действия с ресурсом, используя представление для захвата текущего или предполагаемого состояния этого ресурса и передачи этого представления между компонентами.. Представление - это последовательность байтов плюс метаданные представления для их описания. Другие часто используемые, но менее точные имена для представления включают: документ, файл, и экземпляр или сущность HTTP сообщения.&lt;/p&gt;

&lt;p&gt;Представление состоит из данных, метаданных, описывающих данные, и, иногда, метаданных для описания метаданных (обычно для проверки целостности сообщения). Метаданные представлены в форме пар имя-значение, где имя соответствует стандарту, который определяет структуру и семантику значения. Сообщения ответов могут включать как метаданные представления, так и метаданные ресурса: информацию о ресурсе, которая не является специфичной для поставляемого представления.&lt;/p&gt;

&lt;p&gt;Между компонентами управляющие данные определяют цель сообщения, например запрашиваемое действие или значение ответа. Он также используется для параметризации запросов и переопределения поведения некоторых дефолтных соединительных элементов. Например, поведение кэша может быть изменено управляющими данными, включенными в сообщение запроса или ответа.&lt;/p&gt;

&lt;p&gt;В зависимости от данных управления сообщениями данное представление может указывать текущее состояние запрошенного ресурса, желаемое состояние запрошенного ресурса или значение некоторого другого ресурса, такого как представление входных данных в форме запроса клиента, или представление некоторого состояния ошибки для ответа. Например, удаленная разработка ресурса требует, чтобы автор отправлял представление на сервер, устанавливая таким образом значение для этого ресурса, которое может быть получено последующими запросами. Если набор значений ресурса в данный момент времени состоит из нескольких представлений, согласование контента может использоваться для выбора наилучшего представления для включения в данное сообщение.&lt;/p&gt;

&lt;p&gt;Формат данных представления известен как медиа тип.  Представление может быть включено в сообщение и обработано получателем в соответствии с контрольными данными сообщения и характером типа носителя. Некоторые типы мультимедиа предназначены для автоматической обработки, некоторые предназначены для просмотра пользователем, а некоторые способны на оба. Составные типы мультимедиа могут использоваться для включения нескольких представлений в одно сообщение.&lt;/p&gt;

&lt;p&gt;Конструкция мультимедийного типа может напрямую влиять на воспринимаемую пользователем производительность распределенной гипермедиа-системы. Любые данные, которые должны быть получены до того, как получатель сможет начать рендеринг, увеличивают задержку взаимодействия. Формат данных, который помещает наиболее важную информацию рендеринга вперед, так что исходная информация может быть постепенно увеличена, пока остальная информация принимается, приводит к гораздо лучшей воспринимаемой пользователем производительности, чем формат данных, который должен быть полностью получен до рендеринг может начаться.&lt;/p&gt;

&lt;p&gt;Например, веб-браузер, который может постепенно визуализировать большой HTML-документ во время его получения, обеспечивает значительно лучшую воспринимаемую пользователем производительность, чем та, которая ожидает, пока весь документ полностью не будет получен перед рендерингом, даже если производительность сети одинакова. Обратите внимание, что на возможность визуализации представления также может повлиять выбор контента. Если размеры динамически изменяемых таблиц и встроенных объектов должны быть определены до того, как они могут быть отображены, их появление в области просмотра страницы гипермедиа увеличит ее задержку.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.2 Коннекторы
&lt;/h3&gt;

&lt;p&gt;REST использует различные типы коннекторов, приведенные в таблице &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#tab_5_2" rel="noopener noreferrer"&gt;5-2&lt;/a&gt;, для инкапсулирования доступа к ресурсам и передачи их представлений. Коннекторы представляют собой абстрактный интерфейс для взаимодействия компонентов, упрощая его четким разделением проблем и сокрытием базовой реализации ресурсов и механизма связи. Универсальность интерфейса также обеспечивает заменяемость: если пользователь имеет доступ к системе только с помощью абстрактного интерфейса, реализация омжет быть заменена без влияния на пользователей. Поскольку коннектор управляет сетевым взаимодействием для компонента, информация может быть доступна через несколько взаимодействий для улучшения эффективности и отзывчивости. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/909a8531ecde4ff28fe605eb91bd2561" rel="noopener noreferrer"&gt;Untitled&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Все REST взаимодействия не имею состояния. То есть, каждый запрос содержит всю необходимую информацию для того, чтобы коннектор понял запрос вне зависимости от любых запросов, которые могли предшествовать ему. Это ограничение выполняет четыре функции: 1) устраняет необходимость коннекторам сохранять состояние между запросами, что, тем самым, сокращает потребление физических ресурсов и улучшает расширяемость; 2) позволяет обрабатывать взаимодействия параллельно, не требуя, чтобы механизм обработки понимал семантику взаимодействия; 3) позволяет посреднику просматривать и понимать запрос изолированно, что может быть необходимо, если сервисы перестраиваются динамически; и 4) заставляет всю информацию, которая может влиять на переиспользуемость кэшированного ответа присутствовать в каждом запросе.&lt;/p&gt;

&lt;p&gt;Интерфейс коннектора аналогичен процедурному вызову, но с несколькими важными различиями в передаче параметров и результатов. Входные параметры состоят из данных управления запросом, идентификатора указывающего на цель запроса ресурса  и необязательного представления, выходные - из данных управления ответом, необязательных метаданных ресурса и необязательного представления. С абстрактной точки зрения вызов является синхронным, но оба вида параметров могут передаваться как потоки данных. Другими словами, обработка может вызываться перед тем, как значения параметров будут известны полностью, что позволяет избегать задержек пакетной обработки больших передач данных.&lt;/p&gt;

&lt;p&gt;Основными типами коннектора является клиент и сервер. Существенная разница между этимти двумя в том, что клиент, отправляя запрос, инициирует взаимодействие, а сервер прослушивает соединения и отвечает на запросы. Компонент может содержать коннекторы как клиента, так и сервера.&lt;/p&gt;

&lt;p&gt;Третий тип коннетора - коннектор кэша - может располагаться на интерфейсе клиента или сервера для сохранения кэшируемых ответов на текущие взаимодействия, чтобы их можно было повторно использовать для последующих запрошенных взаимодействий. Кэш может использоваться клиентом для избежания повторного сетевого взаимодействия, или сервером для избежания птовроного процесса генерации ответа, причем оба случая служат для уменьшения задержки взаимодействия. Кэш обычно реализуется в адресном пространстве соединителя, которые его использует.&lt;/p&gt;

&lt;p&gt;Некоторые кэш коннекторы являются общими, что означает, что его кэшированные ответы могут использоваться в ответе клиенту, отличному от того, для которого ответ был получен первоначально. Такое совместное кэширование может быть эффективным для снижения влияния "flash crowds" на нагруженный сервер, особенно когда кэширование организовано иерархически для охвата больших групп пользователей, таких как пользователи внутренней сети компании, клиенты интернет-услуг, провайдера или университеты, разделяющие магистраль национальной сети. Однако, совместное кэширование также может приводить к ошибкам, если кэшированный ответ не совпадает с тем, что было бы получено новым запросом. REST пытается сбалансировать стремление к прозрачности в поведении кэша со стремлением к эффективнмоу использованию сети, а не предполагать, что абсолютная рпозрачность требуется всегда.&lt;/p&gt;

&lt;p&gt;Кэш может определять кэшируемость запроса, поскольку интерфейс является общим, а не специфическим для каждого ресурса. По умолчанию, ответ на запрос поиска кэшируется, а ответы на другие запросы нет. Если какая-либо форма аутентификации пользователя является частью запроса, или если ответ указывает, что он не должен использоваться совместно, тогда ответ может быть кэширован только в кэш общего пользования. Компонент может переопределять эти значения по умолчанию, включив управляющие данные которые помечают взаимодействие как кэшируемое, не кэшируемое или кэшируемое только в течение ограниченного времени. &lt;/p&gt;

&lt;p&gt;Средство распознавания преобразует частичные или полные идентификаторы ресурса в информацию о сетевом аддресе, необходимую для устноавления межкомпонентного соединения. например, большинство URI включают в себя имя хоста DNS в качетсве механизма идентификации полномочий ресурса по его именованию. Чтобы инициировать запрос, веб браузер извлечет имя хоста из URI и использует распознаватель DNS для получения адреса интернет-протокола. Другой пример: некоторые схемы идентификации (например, URN) требуют, чтобы посредник преобразовывал постоянный идентификатор в более переходный адрес для доступа к идентифицированному ресурсу. Использование одного или нескольких промежуточных распознавателей может увеличить долговечность ссылок на ресурсы за счет косвенного обращения, хотя это увеличить задержку запроса.&lt;/p&gt;

&lt;p&gt;Последним типом коннектора является туннель, который просто передает взаимодействия через границу соединения, такую как межсетевой экран или сетевой шлюз более низкого уровня. Единственная причина, по которой он моделируется как часть REST и не абстрагируется как часть сетевой инфраструктуры, в том, что некоторые REST компоненты из поведения активного компонента на поведение туннеля могут переключаться динамически. Основным примером является HTTP прокси, который переключается на туннель в ответ на метод CONNECT запроса, что позволяетс его клиенту напрямую связываться с удаленным сервером используя другом протокол, такой как TLS, который не допускает прокси. Такой туннель исчезает, когда оба конца соединения прекращают связь.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.3 Компоненты
&lt;/h3&gt;

&lt;p&gt;REST компнонеты, выведенные в таблице &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#tab_5_3" rel="noopener noreferrer"&gt;5-3&lt;/a&gt;, типизируются на основе принимаемых ими ролей в событиях приложения.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/d2aff6c030994616a76b6f26fbdd2a0c" rel="noopener noreferrer"&gt;Untitled&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;User agent использует клиентский коннектор для инициирования запроса и становится окончательным его получателем. Основной пример - веб браузер, который предоставляет доступ к информационным сервисам и отрисовывает ответы сервисов в соответсвии с потребностями приложения.&lt;/p&gt;

&lt;p&gt;Origin server использует серверный коннектор для управления пространством имен для запрашиваемого ресурса. Является окончательным источником для представления своих ресурсов и должен быть окончательным получаетел любого запроса, если тот требует изменения значений своих ресурсов. Каждый origin server предоставляет общий интерфейс для своих сервисов в виде иерархии ресурсов. Детали реализации ресурса сокрыты за интерфейсом.&lt;/p&gt;

&lt;p&gt;Для пересылки запросов и ответов промежуточные компоненты ведут себя и как клиент, и как сервер. Компонент proxy является посредником, выбранным клиентом для обеспечения интерфейса инкапсуляции для своих сервисов, трансляции данных, повышения производительности и защиты безопасности. Шлюзовый (a.k.a., reverse proxy) компонент - посредник, навязанный сетевым или исходным сервером для обеспечения инкапсуляции интерфейса других служб, для перевода данных, повышения производительности или обеспечения безопасности. Заметьте, что разница между proxy и шлюзом в том, что клиенту нужно явно определить, когда ему нужно использовать proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.3 Архитектурные представления REST
&lt;/h2&gt;

&lt;p&gt;Теперь, когда у нас есть понимание архитектурных элементов REST в отдельности, мы можем использовать архитектурные представления, чтобы описать, как элементы работают вместе, чтобы сформировать архитектуру. Три типа представлений - процесс, коннектор и данные - полезны для освещения принципов проектирования REST.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3.1 Представление процесса
&lt;/h3&gt;

&lt;p&gt;Процессное представление архитектуры в первую очередь эффективно для выявления взаимосвязей взаимодействия между компонентами, показывая путь данных, когда они проходят через систему. Процессное представление архитектуры в первую очередь эффективно для выявления взаимосвязей взаимодействия между компонентами, показывая путь данных, когда они проходят через систему. На изображении &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_10" rel="noopener noreferrer"&gt;5-10&lt;/a&gt; приведен пример представления процесса из архитектуры на основе REST в конкретном случае во время обработки трех параллельных запросов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Frest_process_view.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Frest_process_view.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_process_view.gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Разделение интересов клиента и сервера REST упрощает реализацию компонентов, снижает сложность семантики коннекткоров, повышает эффективность настройки производительности и масштабируемость компонентов чистого сервера. Многоуровневые системные ограничения позволяют вводить посредников - прокси, шлюзы и межсетевые экраны - в различных точках обмена данными, не изменяя интерфейсы между компонентами, что позволяет им помогать в преобразовании обмена данными или повышать производительность посредством крупномасштабного общего кэширования. REST позволяет выполнять промежуточную обработку, ограничивая сообщения для самоописания: взаимодействие не требует состояния между запросами, стандартные методы и типы мультимедиа используются для указания семантики и обмена информацией, а ответы явно указывают на кешируемость.&lt;/p&gt;

&lt;p&gt;Поскольку компоненты подключаются динамически, их расположение и функции для конкретного действия приложения имеют характеристики, аналогичные стилю конвейера (pipe) и фильтра. Хотя компоненты REST обмениваются данными через двунаправленные потоки, обработка каждого направления независима и поэтому восприимчива к преобразователям потока (фильтрам). Общий интерфейс соединителя позволяет размещать компоненты в потоке на основе свойств каждого запроса или ответа.&lt;/p&gt;

&lt;p&gt;Сервисы могут быть реализованы с использованием сложной иерархии посредников и нескольких серверов распределенного происхождения. Природа REST без сохранения состояния позволяет каждому взаимодействию быть независимым от других, устраняя необходимость в понимании общей топологии компонентов, невыполнимую задачу для архитектуры масштаба веба и позволяя компонентам выступать в качестве пунктов назначения или посредников, определяемых динамически по цели каждого запроса. Коннекторы должны знать о существовании друг друга только во время их взаимодействия, хотя они могут кэшировать существование и возможности других компонентов по соображениям производительности.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3.2 Представление коннектора
&lt;/h3&gt;

&lt;p&gt;Представление архитектуры соединителя концентрируется на механике связи между компонентами. Для архитектуры на основе REST нас особенно интересуют ограничения, которые определяют общий интерфейс ресурсов.&lt;/p&gt;

&lt;p&gt;Клиентские коннекторы проверяют идентификатор ресурса, чтобы выбрать подходящий механизм связи для каждого запроса. Например, клиент может быть сконфигурирован для соединения с конкретным прокси-компонентом, возможно, таким, который действует как фильтр аннотаций, когда идентификатор указывает, что это локальный ресурс. Аналогично, клиент может быть настроен на отклонение запросов для некоторого подмножества идентификаторов.&lt;/p&gt;

&lt;p&gt;REST не ограничивает связь конкретным протоколом, но ограничивает интерфейс между компонентами и, следовательно, дополняет область предположений о взаимодействии и реализации, которые могли бы быть сделаны между компонентами. Например, основным протоколом передачи в Интернете является HTTP, но в архитектуру также входит беспрепятственный доступ к ресурсам, которые создаются на уже существующих сетевых серверах, включая FTP, Gopher и WAIS. Взаимодействие с этими службами ограничено семантикой коннектора REST. Это ограничение жертвует некоторыми из преимуществ других архитектур, таких как взаимодействие с состоянием протокола обратной связи по релевантности, такого как WAIS, чтобы сохранить преимущества единого универсального интерфейса для семантики коннектора. В свою очередь, общий интерфейс позволяет получить доступ к множеству сервисов через один прокси. Если приложению требуются дополнительные возможности другой архитектуры, оно может реализовать и вызвать эти возможности как отдельную систему, работающую параллельно, подобно тому, как веб-архитектура взаимодействует с ресурсами "telnet" и "mailto".&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3.3 Представление данных
&lt;/h3&gt;

&lt;p&gt;Представление данных раскрывает состояние приложения при прохождении информации через компоненты. Поскольку REST специально нацелен на распределенные информационные системы, он рассматривает приложение как связную структуру информации и альтернатив управления, с помощью которых пользователь может выполнять желаемую задачу. Например, поиск слова в онлайн-словаре - это одно из приложений, как, например, путешествие по виртуальному музею или просмотр набора заметок для подготовки к экзамену. Каждое приложение определяет цели для базовой системы, по которым можно измерить производительность системы.&lt;/p&gt;

&lt;p&gt;Взаимодействие компонентов происходит в форме сообщений динамического размера. Сообщения малого или среднего размера используются для семантики управления, но основная часть работы приложения выполняется через сообщения большого размера, содержащие полное представление ресурса. Наиболее частой формой семантики запроса является извлечение представления ресурса (например, метода «GET» в HTTP), который часто можно кэшировать для последующего повторного использования.&lt;/p&gt;

&lt;p&gt;REST концентрирует все состояние управления в представлениях, полученных в ответ на взаимодействия. Цель состоит в том, чтобы повысить расширяемость сервера, устраняя необходимость для сервера поддерживать осведомленность о состоянии клиента за пределами текущего запроса. Поэтому состояние приложения определяется его ожидающими запросами, топологией подключенных компонентов (некоторые из которых могут быть фильтрацией буферизованных данных), активными запросами на этих коннекторах, потоком данных представлений в ответ на эти запросы и обработкой этих представления, как они получены агентом пользователя.&lt;/p&gt;

&lt;p&gt;Приложение достигает устойчивого состояния, когда у него нет невыполненных запросов; то есть он не имеет ожидающих запросов, и все ответы на его текущий набор запросов были полностью получены или получены до такой степени, что их можно рассматривать как поток данных представления. Для приложения браузера это состояние соответствует «веб-странице», включающей первичное представление и вспомогательные представления, такие как встроенные изображения, встроенные апплеты и таблицы стилей. Важность стационарных состояний приложений проявляется в их влиянии как на воспринимаемую пользователем производительность, так и на интенсивность трафика сетевых запросов.&lt;/p&gt;

&lt;p&gt;Воспринимаемая пользователем производительность приложения браузера определяется задержкой между установившимися состояниями: периодом времени между выбором гипермедиа-ссылки на одной веб-странице и моментом представления полезной информации для следующей веб-страницы. Поэтому оптимизация производительности браузера сосредоточена на уменьшении этой задержки связи.&lt;/p&gt;

&lt;p&gt;Поскольку основанные на REST архитектуры обмениваются данными главным образом посредством передачи представлений ресурсов, на задержку могут влиять как структура протоколов связи, так и конструкция форматов данных представления. Возможность постепенной визуализации данных ответа по мере их получения определяется конструкцией типа носителя и доступностью информации макета (визуальные размеры встроенных объектов) в каждом представлении.&lt;/p&gt;

&lt;p&gt;Важное наблюдение: наиболее эффективный сетевой запрос - это тот, который не использует сеть. Другими словами, возможность многократного использования кэшированного ответа приводит к значительному улучшению производительности приложения. Хотя использование кэша добавляет некоторую задержку к каждому отдельному запросу из-за накладных расходов на поиск, средняя задержка запроса значительно уменьшается, когда даже небольшой процент запросов приводит к используемым обращениям к кешу.&lt;/p&gt;

&lt;p&gt;Следующее состояние управления приложением находится в представлении первого запрошенного ресурса, поэтому получение этого первого представления является приоритетом. Поэтому взаимодействие REST улучшается с помощью протоколов, которые «сначала отвечают, а потом думают». Другими словами, протокол, который требует множественных взаимодействий для каждого действия пользователя, для того, чтобы выполнить такие вещи, как согласование возможностей функции перед отправкой ответа на контент, будет значительно медленнее, чем протокол, который сначала отправляет то, что наиболее вероятно будет оптимальным, а затем обеспечивает список альтернатив, которые клиент может получить, если первый ответ неудовлетворителен.&lt;/p&gt;

&lt;p&gt;Состояние приложения контролируется и сохраняется пользовательским агентом и может состоять из представлений от нескольких серверов. В дополнение к освобождению сервера от проблем расширяемости сохранения состояния, это позволяет пользователю напрямую манипулировать состоянием (например, историей веб-браузера), предвидеть изменения в этом состоянии (например, карты ссылок и предварительную выборку представлений) и выполнять переход из одного приложения в другое (например, закладки и диалоги ввода URI).&lt;/p&gt;

&lt;p&gt;Следовательно, модельное приложение представляет собой механизм, который перемещается из одного состояния в другое путем изучения и выбора среди альтернативных переходов состояний в текущем наборе представлений. Не удивительно, что это точно соответствует пользовательскому интерфейсу гипермедиа браузера. Однако стиль не предполагает, что все приложения являются браузерами. Фактически, детали приложения скрыты от сервера с помощью универсального интерфейса коннектора, и, таким образом, пользовательский агент может в равной степени быть автоматическим роботом, выполняющим поиск информации для службы индексирования, личным агентом, который ищет данные, которые соответствуют определенным критериям, или обслуживанием. Паук занят патрулированием информации на предмет неработающих ссылок или измененного контента.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>разбираемся с байткодом v8 [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:36 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/v8-1ioe</link>
      <guid>https://forem.com/scumdograscalhouse/v8-1ioe</guid>
      <description>&lt;p&gt;Вольный перевод статьи Franziska Hinkelmann "&lt;a href="https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775" rel="noopener noreferrer"&gt;Understanding V8’s Bytecode&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Разбираемся с байткодом V8
&lt;/h1&gt;

&lt;p&gt;V8 - это опенсорсный движок для JavaScript, разработанный Google. Chrome, Node.js и многие другие приложения используют V8. В этой статье будет предложено объяснение формата байткода V8, который, на самом деле, станет очень простым для чтения, как только вы поймете некоторые основные концепты.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefqjljva9s89rv50msz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefqjljva9s89rv50msz1.png" alt="https://miro.medium.com/max/450/1*g8Tutq52nx6x44ELgz_UWg.png" width="450" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ignition, lift-off! Интерпретатор Ignition является частью пайплайна компилятора с 2016-го года.&lt;/p&gt;

&lt;p&gt;Когда V8 компилирует JavaScript код, парсер генерирует абстрактное синтаксическое дерево. Синтаксическое дерево - это древовидное представление синтаксической структуры JavaScript кода, из которого интерпретатор Ignition генерирует байткод. TurboFan - это оптимизирующий компилятор, который берет байткод и генерирует из него оптимизированный машинный код.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc6a8r2mdsdad15wrhv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc6a8r2mdsdad15wrhv9.png" alt="https://miro.medium.com/max/1019/1*ZIH_wjqDfZn6NRKsDi9mvA.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Пайплайн компилятора V8&lt;/p&gt;

&lt;p&gt;Если хотите узнать, для чего нам нужно именно два режима выполнения, можете посмотреть мое видео с конференции JSConfEU:&lt;/p&gt;

&lt;p&gt;video: &lt;a href="https://www.youtube.com/watch?v=p-iiEDtpy6I&amp;amp;feature=emb_logo" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=p-iiEDtpy6I&amp;amp;feature=emb_logo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bytecode является абстракцией машинного кода&lt;/strong&gt;. Компилировать байткод в машинный код будет проще, если байткод был спроектирован с одинаковой вычислительной моделью, что и физический CPU. Поэтому интерпретаторы обычно являются регистровыми или стэковыми машинами. &lt;strong&gt;Ignition - регистровая машина с аккумулятором (регистром процессора).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5epl67idmadla72pce3a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5epl67idmadla72pce3a.png" alt="https://miro.medium.com/max/995/1*aal_1sevnb-4UaX8AvUQCg.png" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Байткоды V8 можно рассматривать как маленькие строительные блоки,&lt;/strong&gt; которые выстраивают любую JavaScript функциональность, когда складываются вместе. V8 имеет несколько сотен байткодов. Есть байткоды для операций, вроде &lt;code&gt;Add&lt;/code&gt; и &lt;code&gt;TypeOf&lt;/code&gt;, или для загрузки свойств, вроде &lt;code&gt;LdaNamedProperty&lt;/code&gt;. V8 также имеет несколько специфичных байткодов, вроде &lt;code&gt;CreateObjectLiteral&lt;/code&gt; и &lt;code&gt;SuspendGenerator&lt;/code&gt;. Головной файл &lt;a href="https://github.com/v8/v8/blob/master/src/interpreter/bytecodes.h" rel="noopener noreferrer"&gt;bytecodes.h&lt;/a&gt; определяет полный их список.&lt;/p&gt;

&lt;p&gt;Каждый bytecode определяет входные и выходные данные как операнды регистра. Ignition использует геристры &lt;code&gt;r0, r1, r2, ...&lt;/code&gt; и аккумулятор, который также используют почти все байткоды. Он похож на обычный регистр, но байткоды его не определяют его точно. Например, &lt;code&gt;Add r1&lt;/code&gt; добавляет значение регистра &lt;code&gt;r1&lt;/code&gt; к значению в аккумуляторе, что позволяет сохранять память и хранить байткоды в более коротком виде.&lt;/p&gt;

&lt;p&gt;Множество байткодов начинается с &lt;code&gt;Lda&lt;/code&gt; или &lt;code&gt;Sta&lt;/code&gt;. &lt;strong&gt;&lt;code&gt;a&lt;/code&gt;&lt;/strong&gt; в выражении &lt;code&gt;Ld**a**&lt;/code&gt; и &lt;code&gt;St**a**&lt;/code&gt; обозначает &lt;strong&gt;a&lt;/strong&gt;ккумулятор. Например, &lt;code&gt;LdaSmi [42]&lt;/code&gt; подгружает маленькое целое число (Small Integer - Smi) &lt;code&gt;42&lt;/code&gt; в регистр аккумулятора. &lt;code&gt;Star r0&lt;/code&gt; будет хранить текущее значение регистра &lt;code&gt;r0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Пора взглянуть на байткод реальной функции.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function incrementX(obj) {
  return 1 + obj.x;
}

incrementX({x: 42});  // Компилятор V8 ленив. Если вы не запустите функцию - он ее не интерпретирует.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Если вы хотите посмотреть на байткод V8 в JavaScript коде, то исполните команду с флагом --print-bytecode (для D8 и Node.js версии 8.3 и выше). Для Chrome вам нужно запустить его из командной строки с флагом --js-flags="--print-bytecode", см. &lt;a href="https://www.chromium.org/developers/how-tos/run-chromium-with-flags" rel="noopener noreferrer"&gt;Запуск Chromium с флагами&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node --print-bytecode incrementX.js
...
[generating bytecode for function: incrementX]
Parameter count 2
Frame size 8
  12 E&amp;gt; 0x2ddf8802cf6e @    StackCheck
  19 S&amp;gt; 0x2ddf8802cf6f @    LdaSmi [1]
        0x2ddf8802cf71 @    Star r0
  34 E&amp;gt; 0x2ddf8802cf73 @    LdaNamedProperty a0, [0], [4]
  28 E&amp;gt; 0x2ddf8802cf77 @    Add r0, [6]
  36 S&amp;gt; 0x2ddf8802cf7a @    Return
Constant pool (size = 1)
0x2ddf8802cf21: [FixedArray] in OldSpace
 - map = 0x2ddfb2d02309 &amp;lt;Map(HOLEY_ELEMENTS)&amp;gt;
 - length: 1
           0: 0x2ddf8db91611 &amp;lt;String[1]: x&amp;gt;
Handler Table (size = 16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Большую часть вывода можно игнорировать и фокусироваться только на самих байткодах. Ниже описано шаг за шагом, что значит каждый байткод.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;LdaSmi [1]&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;LdaSmi [1]&lt;/code&gt; загружает постоянное значение &lt;code&gt;1&lt;/code&gt; в аккумулятор.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazubu8c85pt14npusrdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazubu8c85pt14npusrdt.png" alt="https://miro.medium.com/max/311/1*WIECS2Gd701BnheqXrWbag.png" width="311" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Star r0&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Далее, &lt;code&gt;Star r0&lt;/code&gt;  в регистре &lt;code&gt;r0&lt;/code&gt; хранит значение, которое на момент находится в аккумуляторе (&lt;code&gt;1&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ywjzesyld7xa2s8j9ek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ywjzesyld7xa2s8j9ek.png" alt="https://miro.medium.com/max/311/1*271aYN7VC6ltaleyDfwhXg.png" width="311" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;&lt;code&gt;LdaNamedProperty a0, [0], [4]&lt;/code&gt;&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;LdaNamedProperty&lt;/code&gt; загружает именованное свойство &lt;code&gt;a0&lt;/code&gt; в аккумулятор. &lt;code&gt;ai&lt;/code&gt; ссылается на i-ый аргумент &lt;code&gt;incrementX()&lt;/code&gt;. В этом примере мы ищем именованное свойство в &lt;code&gt;a0&lt;/code&gt;, первый аргумент &lt;code&gt;incrementX()&lt;/code&gt;. Имя определяется константой  &lt;code&gt;0&lt;/code&gt;. &lt;code&gt;LdaNamedProperty&lt;/code&gt; использует &lt;code&gt;0&lt;/code&gt; для нахождения имени в отдельной таблице:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- length: 1
           0: 0x2ddf8db91611 &amp;lt;String[1]: x&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, &lt;code&gt;0&lt;/code&gt; отображает значение &lt;code&gt;x&lt;/code&gt;, потому этот байткод загружает &lt;code&gt;obj.x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Для чего используется операнд со значением &lt;code&gt;4&lt;/code&gt;? Это индекс так называемого &lt;em&gt;вектора обратной связи&lt;/em&gt; функции &lt;code&gt;incrementX()&lt;/code&gt;, которые содержит нужную для оптимизаций производительности рантайм информацию.&lt;/p&gt;

&lt;p&gt;Теперь регистры выглядят так:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6smtleh4wsm2qsujrc7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6smtleh4wsm2qsujrc7e.png" alt="https://miro.medium.com/max/311/1*sGFN376VKgf2hWXctBqZnw.png" width="311" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Add r0, [6]&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Последняя инструкция добавляет &lt;code&gt;r0&lt;/code&gt; в аккумулятор, возвращая в результате &lt;code&gt;43&lt;/code&gt;. &lt;code&gt;6&lt;/code&gt; - это еще один индекс вектора обратной связи.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn594q1k6n57tbfatba1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn594q1k6n57tbfatba1w.png" alt="https://miro.medium.com/max/311/1*LAHuYIvZaXX8jH_STNHfmQ.png" width="311" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Return&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Return&lt;/code&gt; возвращает значение аккумулятора. Это конец функции &lt;code&gt;incrementX()&lt;/code&gt;. Вызывающий функцию  &lt;code&gt;incrementX()&lt;/code&gt; операнд получает значение &lt;code&gt;43&lt;/code&gt; из аккумулятора и может начать работать с ним.&lt;/p&gt;

&lt;p&gt;На первый взгляд, байткод V8 может показаться какой-то загадкой, особенно со всей остальной информацией в выводе. Но как только вы поймете что Ignition это регистровая машина с аккумулятором регистра, вы сможете понять за что отвечает большинство байткодов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyadyibo9mmh2p2788h4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyadyibo9mmh2p2788h4.png" alt="https://miro.medium.com/max/1024/1*ZrJKJqBsksWd-8uKM9OvgA.png" width="800" height="3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Примечание: в статье описывается байткод V8 версии 6.2, Chrome версии 62 и Node версии 9. Мы постоянно работаем над улучшением потребления памяти и производительности V8, потому детали в других версиях могут отличаться.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>как начать just-in-time-ить [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:33 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/just-in-time-1nm0</link>
      <guid>https://forem.com/scumdograscalhouse/just-in-time-1nm0</guid>
      <description>&lt;p&gt;Вольный перевод статьи Fedor Indutny "&lt;a href="https://darksi.de/4.how-to-start-jitting/" rel="noopener noreferrer"&gt;How to start JIT-ting&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Как начать Just-In-Time-ить
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Предпосылки
&lt;/h2&gt;

&lt;p&gt;Большинство разработчиков что-то слышало об JIT компиляторах и как они могут замедлять интерпретируемые языки в сравнении с нативным кодом. Однако, не так много их них понимает как именно эта JIT штука работает, и еще меньше из них писали свои собственные компиляторы.&lt;/p&gt;

&lt;p&gt;Как мне кажется, хотя бы базовые знания о внутренностях компилятора могут мощно прокачать понимание запускаемого обеспечением кода.&lt;/p&gt;

&lt;p&gt;В этой статье мы посетим несколько вершин на JIT-острове, и возможно даже реализуем компилятор сами!&lt;/p&gt;

&lt;h2&gt;
  
  
  С чего начнем
&lt;/h2&gt;

&lt;p&gt;Зная основы компилятора, мы можем вв что каждый компилятор трансформирует входные данные какого-то формата (обычно, исходный код) в выходные такого же или другого формата (обычно, в машинный код). JIT комплияторы не исключение.&lt;/p&gt;

&lt;p&gt;Что делает их исключительными, так это то, что они запускаются не раньше времени (как gcc, clang и другие), а Just-In-Time (&lt;em&gt;вовремя&lt;/em&gt;) (т.е. прямо перед выполнением выходных данных комплиятора).&lt;/p&gt;

&lt;p&gt;чтобы начать разрабатывать наш собственный JIT компилятор, понадобится выбрать язык для входных данных. Принимая во внимание &lt;a href="http://adambard.com/blog/top-github-languages-for-2013-so-far/" rel="noopener noreferrer"&gt;ТОП ЯЗЫКОВ 2013 ГОДА НА GITHUB&lt;/a&gt;, JavaScript выглядит подходяще для реализации некоторого ограниченного подмножества с упрощенной семантикой. Более того, мы реализуем JIT компилятор на самом JavaScript. Это можно назвать МЕТА-МЕТА!&lt;/p&gt;

&lt;h2&gt;
  
  
  Абстрактное Синтаксическое Дерево (&lt;strong&gt;AST)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;На вход наш компилятор будет принимать JavaScript и возвращать (и немедленно выполнять) машинный код для очень популярной X64 платформы. Но, посокльку люддям удобнее работать с текстовым представлением, разработчики компиляторов обычно стремятся создать несколько Промежуточных Представлений (Intermediate Representations) перед генерацией окончательного машинного кода.&lt;/p&gt;

&lt;p&gt;Поскольку мы пишем упрощенную версию, одного промежуточного представления будет достаточно, и для этого я выбрал представление в виде Абстрактное Синтаксическое Дерево (Abstract Syntax Tree).&lt;/p&gt;

&lt;p&gt;Получить  AST из JavaScript кода достаточно просто и мы можем выбрать несколько (из десятков) библиотек вроде &lt;a href="https://github.com/ariya/esprima" rel="noopener noreferrer"&gt;esprima&lt;/a&gt;, &lt;a href="https://github.com/ariya/esprima" rel="noopener noreferrer"&gt;uglify-js&lt;/a&gt; и т.д. . Чтобы не отходит далеко от туториала, я порекомендую вам выбрать &lt;a href="https://github.com/ariya/esprima" rel="noopener noreferrer"&gt;esprima&lt;/a&gt;. Он имеет неплохой и четко определенный &lt;a href="https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API" rel="noopener noreferrer"&gt;формат выходных данных&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Например, такой код &lt;code&gt;obj.method(42)&lt;/code&gt; создаст (с помощью &lt;code&gt;esprima.parse("...")&lt;/code&gt;) дерево следующего вида:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ type: 'Program',
  body:
   [ { type: 'ExpressionStatement',
       expression:
        { type: 'CallExpression',
          callee:
           { type: 'MemberExpression',
             computed: false,
             object: { type: 'Identifier', name: 'obj' },
             property: { type: 'Identifier', name: 'method' } },
          arguments: [ { type: 'Literal', value: 42 } ] } } ] }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Машинный код
&lt;/h2&gt;

&lt;p&gt;Давайте обобщим: у нас есть исходный код (&lt;em&gt;check&lt;/em&gt;), его Абстрактное Синтаксическое Дерево (&lt;em&gt;check&lt;/em&gt;), осталось только получить машинный код.&lt;/p&gt;

&lt;p&gt;Если вы уже знакомы с assembly то можете пропустить эту главу поскольку она содержит только базовое введение в тему. ОДнако, если нет, последующая глава, без этих основ,  может показаться вам сложной для понимания. Потому оставайтесь здесь, много времени не займет!&lt;/p&gt;

&lt;p&gt;Язык Assembly - это ближайшее текстовое представление бинарного кода который понимает и(или) может запустить ваш CPU. Учитывая, что процессоры выполнябт код читая и запускаю иснтрукции по одной, может показаться логичным, что одна строка asse,bly программы представляет собой одну инструкцию:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov rax, 1    ; Положить 1 в регистр `rax`
mov rbx, 2    ; Положить 2 в регистр `rbx`
add rax, rbx  ; Вычислить сумму `rax` и `rbx` и положить ее в регистр `rax`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Выходными данными этой прграммы будет являться 3 (при условии, что вы получите их из  &lt;code&gt;rax&lt;/code&gt; регистра). И, как вы уже наверно догадались, программа будет ложить данные в CPU слоты (&lt;a href="http://en.wikipedia.org/wiki/Processor_register" rel="noopener noreferrer"&gt;регистры&lt;/a&gt;) и просить CPU вычислить их сумму.&lt;/p&gt;

&lt;p&gt;Обычно процессоры имеют достаточное количество регистров для хранения результата промежуточных операций, но в некоторых случаях вам понадобится хранить/загружать данные из памяти кмопьютера:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov rax, 1
mov [rbp-8], rbx  ; Сохранить rbx регистр в слот стека
mov rbx, 2
add rax, rbx
mov rbx, [rbp-8]  ; Восстановить rbx регистр из слота стека
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Регистры имеют имена, слоты памяти имеют адреса. Эти ардеа обычно используют &lt;code&gt;[...]&lt;/code&gt; синтаксис. Например, &lt;code&gt;[rbp-8]&lt;/code&gt; означает: возьми значение регистра &lt;code&gt;rbp&lt;/code&gt; , вычти из него &lt;code&gt;8&lt;/code&gt;, и достучись до слота памяти, используя значение результата в качестве адреса.&lt;/p&gt;

&lt;p&gt;Как вы можете видеть мы используем &lt;code&gt;rbp&lt;/code&gt; регистр. &lt;code&gt;rbp&lt;/code&gt; обычно содержит адреса по которым переменные из стека (т.е. переменные, которые хранятся в текущем процедурной &lt;a href="http://en.wikipedia.org/wiki/Stack_(abstract_data_type)" rel="noopener noreferrer"&gt;стеке&lt;/a&gt;) запускаются; &lt;code&gt;8&lt;/code&gt; это размер  &lt;code&gt;rbx&lt;/code&gt; регистра (и всех остальынх регистров, название которые начинается на &lt;code&gt;r&lt;/code&gt;), и поскольку &lt;a href="http://en.wikipedia.org/wiki/Stack_(abstract_data_type)" rel="noopener noreferrer"&gt;стек&lt;/a&gt; растет снизу вверх, нам нужно вычесть его из  &lt;code&gt;rbp&lt;/code&gt; чтобы получить адрес свободного слота.&lt;/p&gt;

&lt;p&gt;Есть много нюансов в программировании на низком уровне и к сожалению я не собираюсь обо всех них здесь рассказывать. Также имейте ввиду что я предоставляю очень поверхностное описание и то что происходит здесь на самом деле  может быть гораздо более сложным.&lt;/p&gt;

&lt;p&gt;Понятий поясненных выше должны быть достаточно чтобы приступить к генерации кода.&lt;/p&gt;

&lt;h2&gt;
  
  
  Генерация кода
&lt;/h2&gt;

&lt;p&gt;Реализовать полностью JavaScript будет довольно сложно, потому сейчас мы реализуем только упрощенную версию арифметического движка.&lt;/p&gt;

&lt;p&gt;Лучшим и простейшим способом сделать это будет с помощью прохода по AST с помощью &lt;a href="http://en.wikipedia.org/wiki/Depth-first_search" rel="noopener noreferrer"&gt;поиска в глубину&lt;/a&gt;, генерирую машинный код для каждого узла. Вы можете подумать как можно генерирвоать машинный код для такого memory-safe языка как JavaScript. Здесь я представлю вам &lt;a href="https://github.com/indutny/jit.js" rel="noopener noreferrer"&gt;jit.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Это node.js модуль (и C++ аддон, по сути) способный генерировать и выполнять машинны код используя assembly-подобный JavaScript синтаксис:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var jit = require('jit.js');

var fn = jit.compile(function() {
  this.Proc(function() {
    this.mov('rax', 42);
    this.Return();
  });
});
console.log(fn());  // 42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Давайте уже напишем это
&lt;/h2&gt;

&lt;p&gt;Таким образом нам осталось сделать только одну вещь - модуль для прохода по AST, сгенерированному с помозью &lt;a href="https://github.com/ariya/esprima" rel="noopener noreferrer"&gt;esprima&lt;/a&gt;. К счастью, учитывая его структуру и наш минималистичный дизайн компилятора это должно быть довольно просто.&lt;/p&gt;

&lt;p&gt;Мы внедрим поддержку:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Числовых литералов (&lt;code&gt;{ type: 'Literal', value: 123 }&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Бинарных выражений с операторами: &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt; (&lt;code&gt;{ type: 'BinaryExpression', operator: '+', left: ... , right: .... }&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Унарных выражений с оператором &lt;code&gt;-&lt;/code&gt; (&lt;code&gt;{ type: 'UnaryExpression', operator: '-', argument: ... }&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Все эти операции выполняются на целых числах, потому не ожидайте что они сработают со значениями вроде &lt;code&gt;0.5&lt;/code&gt;, &lt;code&gt;0.66666&lt;/code&gt; и т.д. .&lt;/p&gt;

&lt;p&gt;Во время обработки выражения мы будем посещать каждый поддерживаемый узел AST и генерировать код, который вернет результат в &lt;code&gt;rax&lt;/code&gt; регистр. Звучить легко, да? Здесь есть одно правило: мы должны сохранять все остальные регистры чистыми после выхода из узла дерева. Другими словами, это значит что мы должны сохранять все использованные регистры и восстанавливать их состояние после того как они не нужны. К счастью, CPU имеет две волшебные операции&lt;code&gt;push&lt;/code&gt; и &lt;code&gt;pop&lt;/code&gt; которые помогут нам с этой задачей.&lt;/p&gt;

&lt;p&gt;Таким получится код (с комментариями):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var jit = require('jit.js'),
    esprima = require('esprima'),
    assert = require('assert');

var ast = esprima.parse(process.argv[2]);

// Compile
var fn = jit.compile(function() {
  // Создаем шаблон для входных данных по умолчанию
  this.Proc(function() {
    visit.call(this, ast);

    // Результат должен быть в 'rax'

    // Создаем шаблон для выходных данных
    this.Return();
  });
});

// Выполнение
console.log(fn());

function visit(ast) {
  if (ast.type === 'Program')
    visitProgram.call(this, ast);
  else if (ast.type === 'Literal')
    visitLiteral.call(this, ast);
  else if (ast.type === 'UnaryExpression')
    visitUnary.call(this, ast);
  else if (ast.type === 'BinaryExpression')
    visitBinary.call(this, ast);
  else
    throw new Error('Неизвестный AST узел: ' + ast.type);
}

function visitProgram(ast) {
  assert.equal(ast.body.length,
               1,
               'Поддерживается только один оператор');
  assert.equal(ast.body[0].type, 'ExpressionStatement');
  visit.call(this, ast.body[0].expression);
}

function visitLiteral(ast) {
  assert.equal(typeof ast.value, 'number');
  assert.equal(ast.value | 0,
               ast.value,
               'Поддерживаются только целочисленные значения');

  this.mov('rax', ast.value);
}

function visitBinary(ast) {
  // Сохраняем 'rbx' после того, как вышли из AST узла
  this.push('rbx');

  // Посещаем правую стороно выражения
  visit.call(this, ast.right);

  // Перемещаем в 'rbx'
  this.mov('rbx', 'rax');

  // Посещаем левую сторну выражения (результат в 'rax')
  visit.call(this, ast.left);

  //
  // Обобщяя, левая сторона хранится в 'rax', правая - в 'rbx'
  //

  // Выполняем бинарную операцию
  if (ast.operator === '+') {
    this.add('rax', 'rbx');
  } else if (ast.operator === '-') {
    this.sub('rax', 'rbx');
  } else if (ast.operator === '*') {
    // Умножение со знаком
    // rax = rax * rbx
    this.imul('rbx');
  } else if (ast.operator === '/') {
    // Сохраняем 'rdx'
    this.push('rdx');

    // idiv выполняет деление rdx:rax на rbx, следовательно, нам нужно очистить rdx
    // перед запуском
    this.xor('rdx', 'rdx');

    // Деление со знаком, rax = rax / rbx
    this.idiv('rbx');

    // Восстанавливаем 'rdx'
    this.pop('rdx');
  } else if (ast.operator === '%') {
    // Сохраняем 'rdx'
    this.push('rdx');

    // Готовимся выполнить idiv
    this.xor('rdx', 'rdx');
    this.idiv('rbx');

    // idiv помещает остаток в 'rdx'
    this.mov('rax', 'rdx');

    // Восстанавливаем 'rdx'
    this.pop('rdx');
  } else {
    throw new Error('Неподдерживаемый бинарный оператор: ' + ast.operator);
  }

  // Восстанавливаем 'rbx'
  this.pop('rbx');

  // Результат в 'rax'
}

function visitUnary(ast) {
  // Посещаем аргумент и складываем результат в 'rax'
  visit.call(this, ast.argument);

  if (ast.operator === '-') {
    // Делаем аргумент отрицательным
    this.neg('rax');
  } else {
    throw new Error('Неподдерживаемый унарный оператор: ' + ast.operator);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Вы можете попробоать сами склонировав исходный код с &lt;a href="https://github.com/indutny/jit.js/tree/master/example/basic" rel="noopener noreferrer"&gt;github&lt;/a&gt;, запустив &lt;code&gt;npm install&lt;/code&gt; и вуаля!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node ./main.js '1 + 2 * 3'
7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>назначаем числа [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:31 +0000</pubDate>
      <link>https://forem.com/scumdograscalhouse/-5fpe</link>
      <guid>https://forem.com/scumdograscalhouse/-5fpe</guid>
      <description>&lt;p&gt;Вольный перевод статьи Fedor Indutny "&lt;a href="https://darksi.de/5.allocating-numbers/" rel="noopener noreferrer"&gt;Allocating numbers&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Назначаем числа
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Цели
&lt;/h2&gt;

&lt;p&gt;В прошлой статье мы создали JIT компилятор, поддерживающий очень ограниченное количесвто подномежств JavaScript: целые числа, бинарные математические операции операторы (&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;), и унарный оператор &lt;code&gt;-&lt;/code&gt;. В этот раз мы расширим его добавлением поддержки чисел с плавающей точкой, а, чтобы процесс стал живее и веселее, мы будем выделять и хранить эти числа в куче.&lt;/p&gt;

&lt;p&gt;Поскольку мы внедряем функциональность небольшими шагами, на этом этапе наша куча не будет иметь сборщика мусора и будет жить внутри чанка памяти фиксированного размера (скажи "ура" простоте!).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Stubs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Отталкиваясь от наших целей мы можем установить внутренние структуры для желаемых фич. По сути, нам понадобится процедура выделения памяти, которая генерирует и возвращает подходящий нашим целям участок памяти.&lt;/p&gt;

&lt;p&gt;Такое выделение может быть сгенерировано для каждого узла AST с помощью серий встроенных assembly инструкций, которые великолепно работают и, что более важно, невероятно быстро исполняются для коротких операций. Но из-за относительного большого размера кодовой базы это процедуры, финальный вывод в виде машинного кода может не поместиться в кэше CPU, вызывая потенциальные проблемы с производительностью всей системы.&lt;/p&gt;

&lt;p&gt;Как правило, это считается плохой практикой. Более лучшим подходом будет будет параметризация таких блоков кода в общие процедуры, называемые &lt;code&gt;stubs&lt;/code&gt; (я взял это название из &lt;a href="https://github.com/v8/v8/blob/master/src/ia32/code-stubs-ia32.cc" rel="noopener noreferrer"&gt;кода v8&lt;/a&gt; и, похоже, они имеют такое же название и в других VM). Для еще большей оптимизации эти процедуры можно компилировать лениво, т.е. мы не должны компилировать участки, которые не используются генерируемым кодом. Эта техника хорошо сказывается на времени компиляции и размере выполняемого кода (и, соответснвенно, кэше CPU).&lt;/p&gt;

&lt;p&gt;К счастью, &lt;a href="https://github.com/indutny/jit.js" rel="noopener noreferrer"&gt;jit.js&lt;/a&gt; позволяет достаточно легко генерировать &lt;em&gt;stubs&lt;/em&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var stubs = jit.stubs();

stubs.define('Allocate', function() {
  // Наш код здесь
  // ....

  // Возвращаемся к месту вызова
  this.Return();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Просто, не так ли? Чтобы использовать это в нашем JIT компиляторе нам нужно передать его в аргументы:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jit.compile(function() {
  // Пояснение:
  // Прочитать адрес стаба 'Allocate' в регистре 'rax' и
  // вызвать его.
  this.stub('rax', 'Allocate');

  this.Return();
}, { stubs: stubs });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Как я заметил выше, сгенерированы и переиспользованы будут только те стабы, которые исподьзовались во вермя процесса компиляции.&lt;/p&gt;

&lt;h2&gt;
  
  
  Куча
&lt;/h2&gt;

&lt;p&gt;Теперь мы можем перейти к выделению памяти. Но сначала давайте взнглянем на структуру и организацию кучи.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Куча&lt;/em&gt; - это место, где JavaScript (и многие другие) VM создает и хранит обхекты (обычно те которые не могут уместиться в регистрах CPU). Некоторые объекты кучи могут содержать ссылки на другие объекты (другими словами, могут ссылаться на них). Все живущие объекты и их ссылки создают ориентированный граф начиная с так называех &lt;em&gt;корней&lt;/em&gt; (который обычно являются глобальными переменными и указателями в стеке).&lt;/p&gt;

&lt;p&gt;И хоть она обычно используется для VM, сборка мусора не обязательна для кучи. Многие виртуальные машины и языки выбирают неуправляемую память вместо того (C/C++ как пример). В таких случаях (как пользователь языка) будет необзодимо явно очищать неиспользуемые ресурсы чтобы не исчерпать память.&lt;/p&gt;

&lt;p&gt;Но по очевидным причинам компилятор подмножеств JavaScript который мы реализуем должен поддерживать как управляемую память так и сборку мусора (что сделаем позже).&lt;/p&gt;

&lt;p&gt;Есть тонны кнги котоыре могут дать вам продвинутое представление о распределении кучи и сборке мусора (я порекомендую &lt;a href="http://www.amazon.com/The-Garbage-Collection-Handbook-Management/dp/1420082795/ref=sr_1_1?ie=UTF8&amp;amp;qid=1383600127&amp;amp;sr=8-1&amp;amp;keywords=garbage+collection+handbook" rel="noopener noreferrer"&gt;The Garbage Collection Handbook&lt;/a&gt;) и и значительно много способов выделения и сборки памяти в куче.&lt;/p&gt;

&lt;p&gt;Обычно вам понадобится выбрать между скоростью выделения и фрагментацией памяти. Но поскольку мы не углубляемся в тему я порекомендую остановиться на методе "bump allocation".&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Bump allocation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;bump allocation для фиксированных страниц работает следующим образом.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Взять чанк памяти фиксированного значения (страница)&lt;/li&gt;
&lt;li&gt;Отдать последующие фрагменты в виде возвращаемого значение процедуры выделения.&lt;/li&gt;
&lt;li&gt;Сжимая или перемещая живущие объекты в новый чанк памяти, запускать сборку мусора и очищать неиспользуемое пространство,когда кончается память, (заменяя ссылки на живущие обхекты).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Используя &lt;a href="https://github.com/indutny/jit.js" rel="noopener noreferrer"&gt;jit.js&lt;/a&gt; и стабы, эта процедура может выглядеть следующим образом:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Создаем чанки памяти фиксированного размера
var page = new Buffer(1024);

// Устанавливаем указатели на начало и конец страницы
var offset = jit.ptr(page);
var end = jit.ptr(page, page.length);

stubs.define('Alloc', function() {

  // Сохраняем регистры 'rbx' и 'rcx'
  this.spill(['rbx', 'rcx'], function() {
    // Загружаем `offset`
    //
    // ПРИМЕЧАНИЕ: Мы будем использовать указатель для переменной `offset`, чтобы была вохможность
    // позже обновить ее
    this.mov('rax', this.ptr(offset));
    this.mov('rax', ['rax']);

    // Конец заргрузки
    //
    // ПРИМЕЧАНИЕ: То же самое относится и к концу страницы, но мы не обновляем его прямо сейчас
    this.mov('rbx', this.ptr(end));
    this.mov('rbx', ['rbx']);

    // Вычисляем новый `offset`
    this.mov('rcx', 'rax');

    this.add('rcx', 16);

    // Проверяем, не переполняется ли буффер фиксированного размера
    this.cmp('rcx', 'rbx');

    // this.j() выполняет условный переход к указанной метке.
    // 'g' означает 'greater'
    // 'overflow' - это имя метки, связанной ниже
    this.j('g', 'overflow');

    // Окей, может двигаться дальше и обновить offset
    this.mov('rbx', this.ptr(offset));
    this.mov(['rbx'], 'rcx');

    // Первый 64-х битный указатель зарезервирован для 'tag',
    // второй имеет значенеи `double`
    this.mov(['rax'], 1);

    // Возвращаем 'rax'
    this.Return();

    // Переполнение :(
    this.bind('overflow')

    // Вызываем javascript функцию!
    // ПРИМЕЧАНИЕ: Штука ниже очень прикольная, но я поясню ее позже
    this.runtime(function() {
      console.log('GC is needed, but not implemented');
    });

    // Краш
    this.int3();

    this.Return();
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Это все! Не совсем просто, но не так уж и сложно!&lt;/p&gt;

&lt;p&gt;Эта процедура будет выдавать последующие куски страницы и даже помечать их (про это поговорим в одном из будующих постов. По сути, они используются определения разных типов объектов кучи. )! &lt;/p&gt;

&lt;p&gt;Стоит отметить пару моментов:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;jit.ptr(buf, offset)&lt;/code&gt; возвращает &lt;code&gt;Buffer&lt;/code&gt;, содержащий указатель на данный  &lt;code&gt;buf&lt;/code&gt; с добавленным к нему &lt;code&gt;offset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;this.spill()&lt;/code&gt; - это процедура для сохранения и восстановления регистров памяти (обычно называется &lt;em&gt;spilling&lt;/em&gt;). Принимает список регистров и коллбэк. Эти регистры будут сохранены перед входом в коллбэк и восстановлены после выхода из него. ПРИМЕЧАНИЕ: ВОсстановленный код будет сгенерирован перед каждым &lt;code&gt;this.Return()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;this.mov(['rbx'], 'rcx')&lt;/code&gt; сохраняет регистр &lt;code&gt;rcx&lt;/code&gt; в ячейку памяти, на которую указывает значение регистра &lt;code&gt;rbx&lt;/code&gt;. ПРИМЕЧАНИЕ: offset также можно указать здесь: &lt;code&gt;this.mov(['rbx', 8], 'rcx')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;jit.js поддерживает ветвления примитивов: &lt;code&gt;this.cmp(a, b)&lt;/code&gt;, &lt;code&gt;this.j(condition, labelName)&lt;/code&gt;, &lt;code&gt;this.j(labelName)&lt;/code&gt;, &lt;code&gt;this.bind(labelName)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Плавающая запятая
&lt;/h1&gt;

&lt;p&gt;Теперь, когда у нас есть &lt;em&gt;предположительно&lt;/em&gt; работающая процедура, давайте вспомним что должно храниться внутри чанков нашей кучи. Мы создали чанки со значением тега 8 байт и содержимым 8 байт. Этого достаточно чтобы хранить &lt;code&gt;double&lt;/code&gt; (как C тип) числа с плавающей запятой.&lt;/p&gt;

&lt;p&gt;Есть множество assembly инструкций для загрузки/хранения и работы с такими числами. Но заметьте что для работы с ними их нужно хранить в разных наборах регистров: &lt;code&gt;xmm0&lt;/code&gt;, &lt;code&gt;xmm1&lt;/code&gt;, ... &lt;code&gt;xmm15&lt;/code&gt;. Хотя, 64-х битные числа с плавающей запятой могут храниться и в регистрах общего назначения: &lt;code&gt;rax&lt;/code&gt;, &lt;code&gt;rbx&lt;/code&gt;, ... Математические операции возможны только с набором регистров &lt;code&gt;xmm&lt;/code&gt;. Ниже несколько инструкций которые присутстввуют в &lt;code&gt;jit.js&lt;/code&gt; и могут быть полезными нашему компилятору:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;movq('xmm', 'gp')&lt;/code&gt; или &lt;code&gt;movq('gp', 'xmm')&lt;/code&gt; для перемещения 64-х битных значений из регистра общего назначения в xmm, или наоборот.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;movsd('xmm', 'xmm')&lt;/code&gt; для перемещения значения из одного xmm к другому.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;addsd&lt;/code&gt;, &lt;code&gt;mulsd&lt;/code&gt;, &lt;code&gt;subsd&lt;/code&gt;, &lt;code&gt;divsd&lt;/code&gt; - сложение, умножение, вычитание, деление.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cvtsi2sd('xmm', 'gp')&lt;/code&gt;, &lt;code&gt;cvts2si('gp', 'xmm')&lt;/code&gt; для концертации целых чисел в double, и наоборот.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;roundsd('mode', 'xmm', 'xmm')&lt;/code&gt; для округления значения регистра &lt;code&gt;src&lt;/code&gt; с помощью указанного режима &lt;code&gt;mode&lt;/code&gt; (одного из: &lt;code&gt;nearest&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;, &lt;code&gt;up&lt;/code&gt;, &lt;code&gt;zero&lt;/code&gt;) и помещения результата в регистр &lt;code&gt;dst&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Используя это священное знание, мы можем исправить наш код, чтобы он работал с числами с плавающей запятой (да, мы пока удалим целочисленную поддержку).:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Compile
var fn = jit.compile(function() {
  // Создаем шаблон для входных данных по умолчанию
  this.Proc(function() {
    visit.call(this, ast);

    // Результат должен быть в 'rax'
    //
    // Создаем шаблон для выходных данных
    this.Return();
  });
}, { stubs: stubs });

// Выполнение
console.log(fn());

function visit(ast) {
  if (ast.type === 'Program')
    visitProgram.call(this, ast);
  else if (ast.type === 'Literal')
    visitLiteral.call(this, ast);
  else if (ast.type === 'UnaryExpression')
    visitUnary.call(this, ast);
  else if (ast.type === 'BinaryExpression')
    visitBinary.call(this, ast);
  else
    throw new Error('Неизвестный AST узел: ' + ast.type);
}

function visitProgram(ast) {
  assert.equal(ast.body.length,
               1,
               'Поддерживается только один оператор');
  assert.equal(ast.body[0].type, 'ExpressionStatement');

  // Конвертируем в целое число имеющийся в 'rax' указатель
  visit.call(this, ast.body[0].expression);

  // Получаем число с плавающей запятой из числа кучи
  this.movq('xmm1', ['rax', 8]);

  // Округляем до нуля
  this.roundsd('zero', 'xmm1', 'xmm1');

  // Конвертируем double в целое число
  this.cvtsd2si('rax', 'xmm1');
}

function visitLiteral(ast) {
  assert.equal(typeof ast.value, 'number');

  // Выделяем новое число кучи
  this.stub('rax', 'Alloc');

  // Сохраняем 'rbx' регистр
  this.spill('rbx', function() {
    this.loadDouble('rbx', ast.value);
    this.mov(['rax', 8], 'rbx');
  });
}

function visitBinary(ast) {
  // Сохраняем 'rbx' после того, как вышли из AST узла
  this.spill('rbx', function() {
    // Посещаем правую сторону выражения
    visit.call(this, ast.right);

    // Перемещаем в 'rbx'
    this.mov('rbx', 'rax');

    // Посещаем левую сторону выражения (результат в 'rax')
    visit.call(this, ast.left);

    //
    // Обобщяя, левая сторона хранится в 'rax', правая - в 'rbx'
    //

    // Загружаем их значения в double представлении
    this.movq('xmm1', ['rax', 8]);
    this.movq('xmm2', ['rbx', 8]);

    // Выполняем бинарную операцию
    if (ast.operator === '+') {
      this.addsd('xmm1', 'xmm2');
    } else if (ast.operator === '-') {
      this.subsd('xmm1', 'xmm2');
    } else if (ast.operator === '*') {
      this.mulsd('xmm1', 'xmm2');
    } else if (ast.operator === '/') {
      this.divsd('xmm1', 'xmm2');
    } else {
      throw new Error('Неподдерживаемый бинарный оператор: ' + ast.operator);
    }

    // Выделяем новое число и складываем в него значение
    this.stub('rax', 'Alloc');
    this.movq(['rax', 8], 'xmm1');
  });
}

function visitUnary(ast) {
  if (ast.operator === '-') {
    // Делаем аргумент отрицательным эмулируя бинарное выражение
    visit.call(this, {
      type: 'BinaryExpression',
      operator: '*',
      left: ast.argument,
      right: { type: 'Literal', value: -1 }
    })
  } else {
    throw new Error('Unsupported unary operator: ' + ast.operator);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
  </channel>
</rss>
