<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Очередной блог фрилансера</title>
	<atom:link href="http://dreamhelg.ru/feed/" rel="self" type="application/rss+xml" />
	<link>http://dreamhelg.ru</link>
	<description></description>
	<lastBuildDate>Wed, 02 Jun 2021 20:55:55 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>
	<item>
		<title>Unit-тесты и оператор of() библиотеки RxJs</title>
		<link>http://dreamhelg.ru/2021/06/02/rxjs-of-operator-and-unit-test-async/</link>
					<comments>http://dreamhelg.ru/2021/06/02/rxjs-of-operator-and-unit-test-async/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Wed, 02 Jun 2021 20:55:55 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[юнит-тесты]]></category>
		<category><![CDATA[jasmine]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[unittest]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2719</guid>

					<description><![CDATA[Можно сколько угодно считать себя магистром юнит-тестирования, а потом сесть и потратить чуть ли не пять часов на решение одной маленькой проблемы. Поэтому, чтобы не так жаль было потраченного времени, делюсь с вами свежедобытым инсайтом. Вдогонку к недавнему выпуску про тестирование асинхронного кода, хочется добавить, что нужно быть очень внимательным разработчиком, когда какая-либо из ваших [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Можно сколько угодно считать себя магистром юнит-тестирования, а потом сесть и потратить чуть ли не пять часов на решение одной маленькой проблемы. Поэтому, чтобы не так жаль было потраченного времени, делюсь с вами свежедобытым инсайтом.</p>



<span id="more-2719"></span>



<p>Вдогонку к недавнему выпуску про тестирование асинхронного кода, хочется добавить, что нужно быть очень внимательным разработчиком, когда какая-либо из ваших функций возвращает <strong>Observable&lt;void></strong>. <br><br>Иногда это сам запрос к серверу не шлет в ответ никакие данные, иногда мы делаем это намеренно, вручную возвращая из какого-то метода оператор <strong>of()</strong>.</p>



<p>Тут и кроется одна неприятная особенность. </p>



<p>Неважно в рабочем ли коде или в тестах, когда мы возвращаем результат <strong>of()</strong>, то внезапно получаем зависший намертво тест. Несмотря на то что коллбэк <strong>done()</strong> вызывается в правильном месте.</p>



<p>Например, вот такой фрагмент из юнит-теста:</p>



<pre class="wp-block-syntaxhighlighter-code">fakeDependency.registerShipment.and.returnValue(of());

service.registerShipment().subscribe(() => {
   expect(clearSpy).toHaveBeenCalled();
   done();
});</pre>



<p>Здесь метод <em>registerShipment</em>, изнутри возвращает метод зависимости <em>fakeDependency.registerShipment</em> с возвращаемым типом <strong>Observable&lt;void></strong>. </p>



<p>И этот тест повиснет на выполнении, а в конечном итоге отвалится с ошибкой таймаута. Потому что <strong>observable </strong>ничего не пришлет и соответственно не завершится.</p>



<p>А все потому, что оператор <strong>of()</strong> делает следующее:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Emit variable amount of values in a sequence and then emits a complete notification.</p><cite>документация RxJS</cite></blockquote>



<p>То есть, оператор отправляет переменное количество значений, а затем завершается. </p>



<p>Но мы не передаем ничего внутри <strong>of()</strong>, соответственно, никаких значений не отправляется, <strong>Observable </strong>не завершается, и подписка на него в тесте зависает. </p>



<p>Поправить все это очень легко, достаточно указать <strong>undefined </strong>в скобках <strong>of()</strong> и проблема решена:</p>



<pre class="wp-block-syntaxhighlighter-code">fakeDependency.registerShipment.and.returnValue(of(undefined));

service.registerShipment().subscribe(() => {
    expect(clearSpy).toHaveBeenCalled();
    done();
});</pre>



<p>Будте аккуратны с возвращаемыми вручную значениями <strong>Observable </strong>и обязательно пишите юнит-тесты на этот код. </p>



<p>На этом все. А теперь пойдемте переписывать все пустые операторы <strong>of() </strong>там, где должен был быть <strong>Observable&lt;void></strong>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2021/06/02/rxjs-of-operator-and-unit-test-async/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Как тестировать асинхронный код в Jasmine</title>
		<link>http://dreamhelg.ru/2021/06/02/how-to-test-async-code/</link>
					<comments>http://dreamhelg.ru/2021/06/02/how-to-test-async-code/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Wed, 02 Jun 2021 20:21:42 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[юнит-тесты]]></category>
		<category><![CDATA[jasmine]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[unittest]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2716</guid>

					<description><![CDATA[Как тестировать Observable? Как тестировать Promise? Какие сложности возникают при написании тестов для асинхронного кода? Какие способы доступны в Jasmine для создания асинхронных тестов? Как можно из асинхронного кода сделать синхронный. Это и многое другое в сегодняшнем выпуске. После просмотра вы научитесь тестировать асинхронный код и поймете, что это гораздо проще, чем кажется. Продолжительность: 46 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Как тестировать Observable? Как тестировать Promise? Какие сложности возникают при написании тестов для асинхронного кода? Какие способы доступны в Jasmine для создания асинхронных тестов? Как можно из асинхронного кода сделать синхронный. Это и многое другое в сегодняшнем выпуске. После просмотра вы научитесь тестировать асинхронный код и поймете, что это гораздо проще, чем кажется.</p>



<span id="more-2716"></span>



<p>Продолжительность: 46 минут Уровень: middle, senior </p>



<p>Тестовое приложение из видео &#8211; <a href="https://github.com/dreamhelg/jasmine-unit-test-async" data-type="URL" data-id="https://github.com/dreamhelg/jasmine-unit-test-async">https://github.com/dreamhelg/jasmine-unit-test-async</a></p>



<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Как тестировать асинхронный код в Jasmine - Unit-тестирование в Angular" width="500" height="281" src="https://www.youtube.com/embed/kHAqGpwPFlg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2021/06/02/how-to-test-async-code/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Как тестировать компоненты &#8211; Unit-тестирование в Angular</title>
		<link>http://dreamhelg.ru/2021/05/16/angular-unit-testing-components/</link>
					<comments>http://dreamhelg.ru/2021/05/16/angular-unit-testing-components/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Sun, 16 May 2021 19:56:52 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[юнит-тесты]]></category>
		<category><![CDATA[jasmine]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[unittest]]></category>
		<category><![CDATA[unittesting]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2711</guid>

					<description><![CDATA[Как писать юнит-тесты к компонентам Angular. В чем разница между тестированием сервисов и компонентов. Как эмулируются события и проверяются инпуты. В чем особенности мока зависимостей. Продолжительность: 55 минут. Уроверь: junior, middle Тестовое приложение из видео — https://github.com/dreamhelg/angular-components-unit-test]]></description>
										<content:encoded><![CDATA[
<p><br>Как писать юнит-тесты к компонентам Angular. В чем разница между тестированием сервисов и компонентов. Как эмулируются события и проверяются инпуты. В чем особенности мока зависимостей. </p>



<p>Продолжительность: 55 минут. <br>Уроверь: junior, middle <br><br>Тестовое приложение из видео — <a href="https://github.com/dreamhelg/angular-components-unit-test" data-type="URL" data-id="https://github.com/dreamhelg/angular-components-unit-test">https://github.com/dreamhelg/angular-components-unit-test</a></p>



<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Как тестировать компоненты - Unit-тестирование в Angular" width="500" height="281" src="https://www.youtube.com/embed/Kj9Z4HFWlv4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2021/05/16/angular-unit-testing-components/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Angular 11, BaseHref и относительные пути в LESS</title>
		<link>http://dreamhelg.ru/2021/01/30/angualr-basehref-relative-path-css/</link>
					<comments>http://dreamhelg.ru/2021/01/30/angualr-basehref-relative-path-css/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 15:37:31 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[basehref]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[mvc]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2699</guid>

					<description><![CDATA[Сегодняшняя статья – скорая помощь для всех, кто столкнулся с неразрешимой проблемой относительных путей в CSS (LESS) и атрибута baseHref. Если ваше angular-приложение в проде размещается не в корне, а в отдельной папке, добро пожаловать под кат, подробное описание проблемы и способ решения, все там.]]></description>
										<content:encoded><![CDATA[
<p>Сегодняшняя статья – скорая помощь для всех, кто столкнулся с неразрешимой проблемой относительных путей в CSS (LESS) и атрибута <strong>baseHref</strong>. Если ваше angular-приложение в проде размещается не в корне, а в отдельной папке, добро пожаловать под кат, подробное описание проблемы и способ решения, все там.</p>



<span id="more-2699"></span>



<h2 class="wp-block-heading">Проблема</h2>



<p>Современные крупные сайты, это, как правило, набор больших и маленьких SPA-приложений, связанных между собой какой-то навигацией. Поэтому ситуация, когда локальное приложение у вас запускается от корня папки, а продовое размещается во внутренней папке, встречается, ну буквально, на каждом шагу.</p>



<p>В чем тут сложность? Конечно же с путями к ассетам. Например, если у нас в компоненте подключена картинка в качестве background-image, то, когда мы собираем приложение в тестовом режиме локально, путь к ней вполне можно указать от корня сайта:</p>



<pre class="wp-block-code"><code>.image {
   background-image: url(/assets/img/my-image.png);
}
</code></pre>



<p>То есть, нам не нужно учитывать иерархию компонентов и структуру приложения. Какая бы вложенность не была, такой путь будет работать, потому что картинки у нас лежат в папке <em>assest</em>s, а сайт работает из корня папки.<br><br>А теперь представим, что наше фронтовое приложение в проде лежит в папке <em>my-app</em>. Тогда, чтобы в проде эта картинка отобразилась, путь к ней должен учитывать еще и эту папку, то есть он должен быть таким:<br></p>



<pre class="wp-block-code"><code>.image {
   background-image: url(/my-app/assets/img/my-image.png);
}
</code></pre>



<p>Для решения этой задачи, достаточно долгое время в Angular использовался специальный параметр. Чтобы понять, как именно, вот вам небольшой экскурс в историю.</p>



<h2 class="wp-block-heading">BaseHref to rescue</h2>



<p><strong>baseHref </strong>– это опция <strong>angular.json</strong>, которая позволяет указать различные пути размещения приложения, локально и на продакшене. </p>



<p>Следвательно, для нашего гипотетического приложения, в секции: <em>architect –&gt; build -&gt; configurations -&gt; production -&gt; baseHref</em>  &#8211; мы указываем значение «<em>my-app</em>». </p>



<pre class="wp-block-code"><code>...
"architect": {
  "build": {
     "configurations": {
         "production": {
              "baseHref": "/my-app/",
...</code></pre>



<p>А в секции serve, оставляем дефолтное значение: «<em>/</em>»</p>



<pre class="wp-block-code"><code>...
"architect": {
   "serve": {
     "options": {
          "baseHref": "/"
...    </code></pre>



<p>Что же фактически происходило, когда мы указывали подобные настройки и выполняли билд приложения для публикации? <br><br>Ко всем путям в CSS добавлялось то значение, которое мы указали в опции baseHref. Если сделать билд приложения с указанными настройками (Angular &lt;= 8), то где-то в недрах файла <em>main.js</em>, вы обнаружите измененные пути:</p>



<pre class="wp-block-code"><code>&#91;"src","/my-app/assets/my-image.png" alt=""]</code></pre>



<p>При этом, локально все по-прежнему работало с путями от корня:</p>



<pre class="wp-block-code"><code>&#91;"src","/assets/my-image.png" alt=""]</code></pre>



<p>Я говорю об этом в прошедшем времени, потому что во время миграции с седьмой версии на восьмую, у разработчиков начались приключения.</p>



<h2 class="wp-block-heading"><strong>Breaking changes и временное решение</strong></h2>



<p>В один прекрасный релиз, команда разработки ангуляра решила, что подобная замена путей в CSS вещь нехорошая и отключила этот функционал. Как следствие, все, кто обновил фреймворк – получили проблему в проде, пропавшие изображения, заданные через CSS, от корня сайта. </p>



<p>К счастью дело было не вполне безнадежное, и если в том же <strong>angular.json</strong> опцию: <em>architect –&gt; build -&gt; configurations -&gt; production -&gt; rebaseRootRelativeCssUrls</em> установить в true – проблема решалась.</p>



<pre class="wp-block-code"><code>...
 "architect": {
   "build": {
     "configurations": {
        "production": {
           "rebaseRootRelativeCssUrls": true,
...</code></pre>



<h2 class="wp-block-heading"><strong>Опять беда</strong></h2>



<p>Тут бы казалось, проблема решена, можно расходиться. Но, внимательный разработчик, смотрит на описание новой опции (на самом деле нет), которое гласит:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>«Change root relative URLs in stylesheets to include base HREF and deploy URL. Use only for compatibility and transition. The behavior of this option is non-standard and will be removed in the next major release.»</p></blockquote>



<p>Или проще говоря, праздник на вашей улице ненадолго, опцию удалят в следующем мажорном релизе. Сказано – сделано, в версии 11 опцию выпилили, ничего не оставив взамен. </p>



<p>Точнее так: взамен оставили рекомендацию всегда и везде в CSS использовать относительные пути.&nbsp; Что конечно хорошо и правильно, если б не одна проблема. </p>



<p><strong>Относительные пути нельзя использовать во вложенных компонентах, в режиме serve, приложение просто не соберется, выдав ошибку:</strong></p>



<pre class="wp-block-code"><code>Failed to compile.
./src/app/inner/inner.component.less
Module Error (from ./node_modules/postcss-loader/src/index.js):
(Emitted value instead of an instance of Error) CssSyntaxError: \projects\path-fix\src\app\inner\inner.component.less:2:18: Can't resolve 'assets/my-image.png' in 'I:\projects\path-fix\src\app\inner'
  1 | .someclass {
> 2 |   background: url("assets/my-image.png");
</code></pre>



<p>Да и с чего бы ему собираться, внутри папки с компонентом у нас ассетов нет, они в корне лежат.</p>



<p>А вложенные компоненты в angular, это все, кроме родительского. Ну, то есть можно конечно писать нечто в CSS &nbsp;<em>«/../../../../../assest/my-image.png</em>», но кому в здравом уме, это понравится делать?</p>



<p>Особенно, когда у вас давно написанное приложение, с матрешкой из овер 9000 компонентов, которые раньше прекрасно работали с коротким путем /assets.</p>



<h2 class="wp-block-heading">Решение</h2>



<p>К счастью есть решение, максимально простое и удобное, работающее с LESS. Достаточно в пути к ассетам, указать карет ^</p>



<pre class="wp-block-code"><code>backgorund-image: url("^assets/my-image.png")</code></pre>



<p>Теперь в режиме serve – приложение соберется без проблем, потому что при билде приложения, postCSS-плагин этот символ просто игнорирует, возвращая указанный путь. </p>



<p>Таким образом, в режиме серва, мы можем использовать относительные пути к ассетам, в любом компоненте, не беспокоясь об иерархии приложения и различном размещении его локально и в проде.</p>



<p>Проверено на Angular 11.1.1<br><a href="https://github.com/angular/angular-cli/issues/12797#issuecomment-598534241" data-type="URL" data-id="https://github.com/angular/angular-cli/issues/12797#issuecomment-598534241">Источник решения</a></p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2021/01/30/angualr-basehref-relative-path-css/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Как сделать адаптивный по высоте/ширине инпут</title>
		<link>http://dreamhelg.ru/2020/12/20/how-to-create-adaptive-input/</link>
					<comments>http://dreamhelg.ru/2020/12/20/how-to-create-adaptive-input/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Sun, 20 Dec 2020 12:51:33 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[верстка]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[input]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[textarea]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2660</guid>

					<description><![CDATA[Рано или поздно вы получите от дизайнера макет, где встретится он &#8211; с виду вроде бы обычный текстовый инпут, а на деле, меняющий свою ширину или высоту, в зависимости от содержимого. Хорошие новости &#8211; отчаиваться не будем, это возможно. Причем, возможно достаточно разнообразными способами, об одном из них сегодня и поговорим. Проблема Прежде чем кидаться [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Рано или поздно вы получите от дизайнера макет, где встретится он &#8211; с виду вроде бы обычный текстовый инпут, а на деле, меняющий свою ширину или высоту, в зависимости от содержимого. Хорошие новости &#8211; отчаиваться не будем, это возможно. Причем, возможно достаточно разнообразными способами, об одном из них сегодня и поговорим. </p>



<span id="more-2660"></span>



<p></p>



<h2 class="wp-block-heading">Проблема</h2>



<p>Прежде чем кидаться решать проблему &#8211; ее нужно осознать. С чего бы начать? Все зависит от поставленной вам задачи. Например: если требуется, чтобы инпут расширялся в ширину &#8211; это одна проблема. Если нужно чтобы он рос в высоту &#8211; это проблема другая. А если нужно чтобы сразу во все стороны, тут можно начинать страдать, дело-то непростое. </p>



<p>Главная проблема состоит в том, что элементы ввода не умеют растягиваться по своему содержимому. Это очень досадно и конечно мир был бы лучше, если бы они умели, но увы. Более того, однострочное текстовое поле &#8211; input вообще бессмысленно растягивать в высоту, содержимое все равно не ляжет в несколько строк, так уж он устроен. Поэтому дальше начинаются любимые фронтендерами лайфхаки.</p>



<h2 class="wp-block-heading">Варианты решения</h2>



<p>Самый первый, приходящий в топ выдачи гугла &#8211; это просто взять и заменить инпут на div с атрибутом <code><strong>contenteditable</strong></code>&nbsp; &#8211; и вот оно, проблема решена! Вот вам адаптивный по высоте/ширине див и редактируемое содержимое. Но если для вас (как и для меня) это не вариант, то смотрим дальше.</p>



<p>Следующий вариант не назовешь слишком изящным, но работающий вот что важно. Основан на том, что где-то позади самого текстового поля мы прячем невидимый блок, куда дублируем содержимое текстового поля при любом изменении. Блок изменяет свои размеры по содержимому, мы вычисляем их с помощью ява-скрипта и присваиваем текстовому полю. Если вас не смущает дополнительный мусор на странице в виде такого вот костыля &#8211; смело можно брать на вооружение.</p>



<p>Ну и третий вариант, который мне больше всего пришелся по душе &#8211; это все-таки вычислять содержимое текстового инпута и адаптировать его ширину/высоту, чтобы все помещалось. Давайте на него посмотрим чуть более внимательно.</p>



<h2 class="wp-block-heading">Выбираем правильный инпут</h2>



<p>Прежде всего, понадобится заменить input на textarea. Поскольку инпуты бесполезно наращивать в высоту, лучше сразу возьмем многострочное поле и будем делать вид, что это однострочный инпут.</p>



<p>Добавляем немного стилей, чтобы замаскировать истинную сущность нашего текстового поля. </p>



[code lang=&#8221;css&#8221;]
.textarea {
  font-size: 24px;
  font-family: Arial;
  border-radius: 8px;
  resize: none;
  padding: 5px;
  overflow: hidden;
  box-sizing: border-box;
  height: 40px;
  min-height: 40px;
  width: 400px;
  margin-bottom: 15px;
}
[/code]



<p>Тут следует обратить внимание на три обязательных параметра, без которых ничего не сработает: <em>box-sizing, height, min-height</em>.</p>



<p><strong>box-sizing: border-box</strong></p>



<p>В обязательном порядке <em>box-sizing</em> должен быть в значении <em>border-box</em>, чтобы когда вы добавите паддинг в текстовом поле, а вы его обязательно добавите, он не увеличивал дополнительно высоту или ширину текстового поля, а откладывался бы вовнутрь.</p>



<p><strong>height и min-height</strong> <strong>(width и min-width)</strong></p>



<p>Тут все просто: <em>height</em> &#8211; чтобы изначально <em>textarea</em> выглядела бы как <em>input</em>. <em>min-height</em> &#8211; чтобы при сбросе значения высоты текстовое поле не прыгало, а оставалось минимально необходимого размера. Все это справедливо и для свойств <em>width </em>и <em>min-width.</em></p>



<h2 class="wp-block-heading">Немного теории</h2>



<p>Принцип действия очень простой. У каждого DOM-элемента, который может содержать контент, есть readonly-свойство <strong>scrollHeight </strong>(<strong>scrollWidth</strong>) которое и содержит так нужную нам, истинную высоту элемента, такую, при которой все содержимое этого элемента будет видно пользователю. Все, что нам требуется &#8211; при изменении текстового поля, вычислять значение <em>scrollHeight</em>/<em>scrollWidth </em>и присваивать его текстовому полю. </p>



<p>А для того, чтобы текстовое поле могло еще и возвращаться к своему исходному виду, перед изменением, будем сбрасывать установленную ранее высоту до нуля, для того чтобы свойство scrollHeight корректно бы показало нам, есть ли контент, не умещающийся в текстовом поле.</p>



<h2 class="wp-block-heading">Немного vanilla JS-практики</h2>



<p>Специально не использовала никаких фреймворков, для большей гибкости. Любым удобным способом получаем текстовое поле со страницы и добавляем обработчик события <strong>input</strong></p>



[code lang=&#8221;javascript&#8221;]
const textareaHeight = document.getElementById(&quot;text&quot;);

textareaHeight.addEventListener(&quot;input&quot;, (event) =&amp;gt; {
  textareaHeight.style.height = 0;
  textareaHeight.style.height = textareaHeight.scrollHeight + &quot;px&quot;;
})
[/code]



<p>Это ввод с клавиатуры. Не стоит забывать, что текстовые поля бывают заполняются автоматически, извне. Для этого добавляем отдельный метод вставки значения:</p>



[code lang=&#8221;javascript&#8221;]
function setValue(text: string) {
  const textarea = document.getElementById(&quot;text&quot;);
  textarea.style.height = 0;
  textarea.value = text;
  textarea.style.height = textarea.scrollHeight + &quot;px&quot;;
}
[/code]



<p>Ну и давайте для ширины тоже сделаем. Все то же самое, только тут уже берем input, и высоту меняем на ширину.</p>



[code lang=&#8221;javascript&#8221;]
const textInput = document.getElementById(&quot;text&quot;);

textInput.addEventListener(&quot;input&quot;, (event) =&amp;gt; {
  textInput.style.width =  0;
  textInput.style.width = textInput.scrollWidth + &quot;px&quot;;
})

function setValue(text: string) {
  const textInput = document.getElementById(&quot;text&quot;);
  textInput.style.width = 0;
  textInput.value = text;
  textInput.style.width = textInput.scrollWidth + &quot;px&quot;;
}
[/code]



<h2 class="wp-block-heading">Результат</h2>



<p>Вот такие поля получились. Несложно и недолго.<br><br><strong>Адаптивный инпут, растущий в высоту:</strong></p>



[codepen_embed height=&#8221;265&#8243; theme_id=&#8221;dark&#8221; slug_hash=&#8221;MWebvEV&#8221; default_tab=&#8221;js,result&#8221; user=&#8221;dreamhelg&#8221;]See the Pen <a href="https://codepen.io/dreamhelg/pen/MWebvEV">Adaptive Height Text Input with Vanilla JS</a> by dreamhelg (<a href="https://codepen.io/dreamhelg">@dreamhelg</a>) on <a href="https://codepen.io">CodePen</a>.[/codepen_embed]



<p></p>



<p><strong>Адаптивный инпут, растущий в ширину:</strong></p>



[codepen_embed height=&#8221;265&#8243; theme_id=&#8221;dark&#8221; slug_hash=&#8221;JjRyozz&#8221; default_tab=&#8221;js,result&#8221; user=&#8221;dreamhelg&#8221;]See the Pen <a href="https://codepen.io/dreamhelg/pen/JjRyozz">Adaptive Width Text Input with Vanilla JS</a> by dreamhelg (<a href="https://codepen.io/dreamhelg">@dreamhelg</a>) on <a href="https://codepen.io">CodePen</a>.[/codepen_embed]



<h2 class="wp-block-heading">Angular Директива</h2>



<p>Для тех, кто дочитал аж до сюда &#8211; бонус. Если лень самостоятельно упаковать все это в директиву, я уже все сделала за вас, смотрите:<br></p>



[code lang=&#8221;javascript&#8221;]

@Directive({
  selector: &quot;[adaptiveInputDirective]&quot;
})
export class AdaptiveInputDirective implements OnInit {
  @Input() horizontal: boolean;

  constructor(private element: ElementRef) {}

  ngOnInit() {
    if (this.horizontal) {
      this.element.nativeElement.style.whiteSpace = &quot;nowrap&quot;;
    }
  }

  @HostListener(&quot;ngModelChange&quot;, [&quot;$event&quot;])
  onChange(): void {
    const input = this.element.nativeElement;

    if (this.horizontal) {
      input.style.width = 0;
      input.style.width = input.scrollWidth + &quot;px&quot;;
    } else {
      input.style.height = 0;
      input.style.height = input.scrollHeight + &quot;px&quot;;
    }
  }
}
[/code]



<p>Здесь используется уже знакомый вам декоратор @HostListener. А если еще не знакомый, то обязательно почитайте <a href="http://dreamhelg.ru/2020/12/angular-hostlistener/" data-type="post" data-id="2649">статью об этом</a>. </p>



<p><strong>Пример использования:</strong><br></p>



[code lang=&#8221;html&#8221;]
&lt;textarea formcontrolname=&quot;myText&quot; adaptiveinputdirective=&quot;&quot;&gt;&lt;/textarea&gt;
[/code]



<p>Да, можно обойтись одним элементом &#8211; textarea. Просто, если нужна адаптивность в ширину &#8211; добавим CSS-свойство <em>white-space: nowrap</em> &#8211; запрет на перенос строк.</p>



<p>В остальном, все, о чем говорили, плюс &#8211; настраиваемый параметр <strong>horizontal</strong> указывающий что тянуть, ширину или высоту. Выбирайте нужный вариант и ни в чем не отказывайте своему дизайнеру.</p>



<p><a href="https://stackblitz.com/edit/angular-adaptive-input-directive?file=src/app/app.component.html" data-type="URL" data-id="https://stackblitz.com/edit/angular-adaptive-input-directive?file=src/app/app.component.html">Посмотреть как работает на stackblitz.com</a></p>



<p>Приходилось вам сталкиваться с подобной  задачей? Как решали?</p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2020/12/20/how-to-create-adaptive-input/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Jasmine SpyOnProperty. Unit-тестирование в Angular</title>
		<link>http://dreamhelg.ru/2020/12/11/unit-test-jasmine-spyonproperty/</link>
					<comments>http://dreamhelg.ru/2020/12/11/unit-test-jasmine-spyonproperty/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Fri, 11 Dec 2020 08:27:11 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[юнит-тесты]]></category>
		<category><![CDATA[jasmine]]></category>
		<category><![CDATA[jasminespy]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[karma]]></category>
		<category><![CDATA[spyOnPrpoperty]]></category>
		<category><![CDATA[unittest]]></category>
		<category><![CDATA[unittesting]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2661</guid>

					<description><![CDATA[Как использовать Jasmine Spy для геттеров и сеттеров? Какие сложности могут возникнуть в ходе использования SpyOnProperty? Как можно скомбинировать мок зависимости, с геттером, методами и статическими полями класса. Все ответы в текущем видео, скорее смотри. Продолжительность: 27 минут Уровень: Middle, Senior Тестовое приложение из видео]]></description>
										<content:encoded><![CDATA[
<p>Как использовать Jasmine Spy для геттеров и сеттеров? Какие сложности могут возникнуть в ходе использования SpyOnProperty? Как можно скомбинировать мок зависимости, с геттером, методами и статическими полями класса. Все ответы в текущем видео, скорее смотри. </p>



<figure class="wp-block-embed-youtube wp-block-embed is-type-rich is-provider-embed-handler wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Jasmine SpyOnProperty - Unit-тестирование в Angular" width="500" height="281" src="https://www.youtube.com/embed/biorjXFjG6c?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p><strong>Продолжительность</strong>: 27 минут <br><strong>Уровень:</strong> Middle, Senior </p>



<p><a href="https://github.com/dreamhelg/angular-jasmine-spy-on-property">Тестовое приложение из видео</a></p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2020/12/11/unit-test-jasmine-spyonproperty/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Angular @HostListener. Легко и просто добавляем обработчики событий</title>
		<link>http://dreamhelg.ru/2020/12/05/angular-hostlistener/</link>
					<comments>http://dreamhelg.ru/2020/12/05/angular-hostlistener/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Sat, 05 Dec 2020 13:36:32 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[eventListener]]></category>
		<category><![CDATA[events]]></category>
		<category><![CDATA[hostlistener]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[spa]]></category>
		<category><![CDATA[webdev]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2649</guid>

					<description><![CDATA[Насколько часто нам требуется добавлять обработчик какого-то события на страницу? Да практически постоянно. Тем радостнее узнать, что команда Angular позаботилась об этом как следует и создала простой и изящный способ &#8211; функцию декоратор @HostListener. Пользоваться просто и легко, если вы хотя бы раз видели функции-декораторы в Angular. Если не видели, тоже ничего сложного: Первый строковый [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Насколько часто нам требуется добавлять обработчик какого-то события на страницу? Да практически постоянно. Тем радостнее узнать, что команда Angular позаботилась об этом как следует и создала простой и изящный способ &#8211; функцию декоратор <strong>@HostListener</strong>. </p>



<span id="more-2649"></span>



<p>Пользоваться просто и легко, если вы хотя бы раз видели функции-декораторы в Angular. Если не видели, тоже ничего сложного:</p>



<pre class="wp-block-code"><code>@HostListener('eventName', &#91;args])</code></pre>



<p>Первый строковый аргумент &#8211; название события DOM, которое мы собираемся слушать и как-то реагировать на его появление. Давно знакомые и родные: click, focus, blur, keypress и т.д. <br>Второе  &#8211; необязательный массив аргументов, которые можно передать функции-обработчику, при возникновении события.</p>



<p>Осталось указать функцию-обработчик, которая будет запускаться каждый раз, когда возникает нужное нам событие.</p>



<p>Например, мы хотим обрабатывать событие click у компонента, выводя алерт с сообщением. Пример учебный и выполнен профессионалами, пожалуйста не повторяйте его на живых людях!</p>



<pre class="wp-block-code"><code>@HostListener('click')
onClick(): void {
  alert('хуяк!');
}</code></pre>



<p>Как видите, максимально просто и быстро. Стоит упомянуть, что текущее событие будет срабатывать только по клику внутри компонента. И если компонент у вас небольшой по размеру, клик за его пределами обработан не будет.</p>



<p>Что если, внутри компонента нам нужно повесить обработчик события, который будет срабатывать глобально, а не только в текущем компоненте? Тоже достаточно просто. Перед названием события, добавляете: &#8220;window:&#8221; или &#8220;document:&#8221; и вуаля! Событие слушается глобально по всей странице:</p>



<pre class="wp-block-code"><code>@HostListener('document:click')
onClick(): void {
alert('хуяк!');
}</code></pre>



<p>Давайте представим другую ситуацию, у нас есть компонент бокового меню, которое должно закрываться по клику снаружи компонента. Для этого как раз и понадобится параметр $event.target, в качестве второго аргумента.</p>



<pre class="wp-block-code"><code>  @HostListener("document:click", &#91;"$event.target"])
  onClick(element: Element): void {
    if (!element.closest(".user-menu")) this.isOpen = false;
  }</code></pre>



<ol class="wp-block-list"><li>Вешаем глобальный обработчик события клик на документ внутри компонента &#8211; <strong>document:click</strong></li><li>Вводим параметр $<strong>event.target</strong> &#8211; содержащий dom-элемент, по которому случился клик</li><li>Определяем родителя этого элемента &#8211; и если это не наш компонент меню &#8211; закрываем меню</li><li>Профит!</li></ol>



<p>Если нужно обрабатывать нажатие определенной клавиши &#8211; нет ничего проще. Забудьте про проверку keyCode, она в прошлом.</p>



<pre class="wp-block-code"><code>@HostListener('keydown.enter')
onEnterClick(): void {
  alert('нажата клавиша Enter!');
}</code></pre>



<p>Да, вам не показалось. Просто точка и название клавиши. </p>



<p>А если нужна обработка комбинации клавиш? Ни слова больше!</p>



<pre class="wp-block-code"><code>@HostListener('document:keydown.control.r', &#91;'$event'])
onControlPClick(event: KeyboardEvent): void {
event.preventDefault();
alert('Не пытайтесь перезагрузить страницу!');
}</code></pre>



<p>Здесь уже обработчик глобальный, поэтому не будет лишним сделать <strong>preventDefault()</strong> чтобы наш код сработал, вместо перезагрузки страницы. На всякий случай сообщаю, пользователь никогда в жизни не вернется на ваш сайт, увидев подобное.</p>



<p><em><strong>Хозяйке на заметку</strong>: при таком указании клавиш, раскладка клавиатуры будет иметь значение, поэтому на русской раскладке, например, событие не сработает.</em></p>



<p>Что еще нужно знать? Обработчик, навешенный через <strong>@HostListener</strong> будет жить, пока жив его компонент. Способа его корректно удалить не задекларировано, поэтому вы сильно-то не увлекайтесь. И в случае чего, используете контролируемый способ &#8211; старый добрый <strong>addEventListener</strong>.</p>



<p>Спасибо за внимание &#8211; лайк, шер, репост.</p>



<p><a rel="noreferrer noopener" href="https://stackblitz.com/edit/angular-host-listener-example?file=src/app/app.component.ts" target="_blank">Учебный пример с работающими событиями.</a></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2020/12/05/angular-hostlistener/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Unit-тестирование в Angular. Как использовать Jasmine Spy?</title>
		<link>http://dreamhelg.ru/2020/11/12/angular-unit-testing-jasmine-spy/</link>
					<comments>http://dreamhelg.ru/2020/11/12/angular-unit-testing-jasmine-spy/#respond</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Thu, 12 Nov 2020 05:32:52 +0000</pubDate>
				<category><![CDATA[angular]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[юнит-тесты]]></category>
		<category><![CDATA[callFake]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[frontend]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[jasmine]]></category>
		<category><![CDATA[jasminespy]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[learning]]></category>
		<category><![CDATA[learnjs]]></category>
		<category><![CDATA[returnValue]]></category>
		<category><![CDATA[screencast]]></category>
		<category><![CDATA[spy]]></category>
		<category><![CDATA[spyOn]]></category>
		<category><![CDATA[testing spy]]></category>
		<category><![CDATA[typescript]]></category>
		<category><![CDATA[unittest]]></category>
		<category><![CDATA[unittesting]]></category>
		<category><![CDATA[webdev]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2642</guid>

					<description><![CDATA[Что такое Jasmine Spy и как он может помочь с тестами. Чем отличается spyOn от createSpy. В чем разница между callFake() и callThrough(). Как самостоятельно сделать, а потом починить flacky-тесты. Очень подробно, с примерами, ошибками и слезами радости.  ]]></description>
										<content:encoded><![CDATA[<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/KPUmJbTY7Ds" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen"></iframe><br />
Продолжительность: 55 минут.<br />
Уроверь: junior, middle</p>
<p><a href="https://github.com/dreamhelg/angular-unit-test-jasmine-spy">Тестовое приложение из видео на гитхабе</a></p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2020/11/12/angular-unit-testing-jasmine-spy/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>О DateTime в UTC замолвите слово</title>
		<link>http://dreamhelg.ru/2020/09/14/js-datetime-utc/</link>
					<comments>http://dreamhelg.ru/2020/09/14/js-datetime-utc/#comments</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Sun, 13 Sep 2020 21:54:00 +0000</pubDate>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[date]]></category>
		<category><![CDATA[dateTime]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[UTC]]></category>
		<category><![CDATA[дата]]></category>
		<category><![CDATA[конвертация даты]]></category>
		<category><![CDATA[ява-скрипт]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2637</guid>

					<description><![CDATA[Давайте на минуточку вспомним то, что все и так знают. Дата в UTC - это дата по Гринвичу, то есть GMT±0:00.
Соответственно, если мы получаем с сервера дату/время в UTC, мы должны конвертировать ее с учетом нашего локального часового пояса. Как это делается?]]></description>
										<content:encoded><![CDATA[<p>Давайте на минуточку вспомним то, что все и так знают. Дата в UTC &#8211; это дата по Гринвичу, то есть GMT±0:00.</p>
<p>Соответственно, если мы получаем с сервера дату/время в UTC, мы должны конвертировать ее с учетом нашего локального часового пояса.<br />
Для этого в яваскрипте есть разные способы.</p>
<p><span id="more-2637"></span></p>
<p>Самый простой, это создать объект даты из полученной строки, а затем вызвать метод toString()</p>
<pre class="lang:js decode:true">const date = new Date('2020-09-13T21:11:32+0000');
date.toString(); // выведет Mon Sep 14 2020 00:11:32 GMT+0300 (Moscow Standard Time)</pre>
<p>Или можно вот так, например, получив разницу во времени:</p>
<pre class="lang:js decode:true ">const date = new Date('2020-09-13T21:11:32+0000');
const difference = date.getTimezoneOffset();
const localDate = new Date(date.getTime() + difference).toString(); // так же выведет Mon Sep 14 2020 00:11:32 GMT+0300 (Moscow Standard Time)</pre>
<p>&nbsp;</p>
<p>Но прежде, чем начинать что-то куда-то конвертировать, присмотритесь внимательно к дате, которая пришла с сервера. Вполне может оказаться, что дата возвращается в формате ISO 8601 &#8211; что есть международный формат форматирования даты и времени. Он, в том числе, указывает часовой сдвиг либо говорит нам о том, что дата в UTC.</p>
<p><strong>Как это выглядит?</strong></p>
<pre class="lang:js decode:true ">1994-11-05T08:15:30-05:00 // -5 часов по Гринвичу
1994-11-05T13:15:30Z // буква Z в конце означает 0 часов по Гринвичу</pre>
<p style="text-align: center;"><strong>И вот если в вашей дате, пришедшей с сервера, обнаруживается буква Z на конце или часовой сдвиг, как в примере выше, никакая конвертация даты на клиенте не нужна!</strong></p>
<p>Достаточно создать объект даты из полученной строки:</p>
<pre class="lang:js decode:true ">const localDate = new Date('2020-08-31T12:27:43Z'); //Mon Aug 31 2020 15:27:43 GMT+0300 (Moscow Standard Time)</pre>
<p><em><strong>localDate</strong> </em>уже и будет содержать верную дату, с учетом вашего часового пояса. Создание new Date() из строки формата ISO 8601 все сделает за вас.</p>
<p>Возможно это сэкономит вам немного времени.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2020/09/14/js-datetime-utc/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Шпаргалка по версиям npm-пакетов</title>
		<link>http://dreamhelg.ru/2020/07/25/npm-packages-syntax-howto/</link>
					<comments>http://dreamhelg.ru/2020/07/25/npm-packages-syntax-howto/#comments</comments>
		
		<dc:creator><![CDATA[dreamhelg]]></dc:creator>
		<pubDate>Sat, 25 Jul 2020 09:30:16 +0000</pubDate>
				<category><![CDATA[nodejs]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[frontend]]></category>
		<category><![CDATA[npm]]></category>
		<category><![CDATA[версионирование]]></category>
		<category><![CDATA[пакеты]]></category>
		<guid isPermaLink="false">http://dreamhelg.ru/?p=2634</guid>

					<description><![CDATA[Версия любого npm-пакета состоит из трех цифр. Пример &#8211; 2.1.0 Первая цифра &#8211; мажор, ломающие изменения. Вторая цифра &#8211; минор, добавление функционала, без нарушения обратной совместимости Третья цифра &#8211; патч, обратно совместимые фиксы багов. В package.json, в списке пакетов, следующий синтаксис: ~ только патч-версия пакета. То есть, если указано &#8220;~1.5.0&#8221;, это значит можно обновиться например [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Версия любого npm-пакета состоит из трех цифр. Пример &#8211; 2.1.0</p>
<p><strong>Первая цифра</strong> &#8211; мажор, ломающие изменения.<br />
<strong>Вторая цифра</strong> &#8211; минор, добавление функционала, без нарушения обратной совместимости<br />
<strong>Третья цифра</strong> &#8211; патч, обратно совместимые фиксы багов.</p>
<p><strong>В package.json, в списке пакетов, следующий синтаксис:</strong></p>
<p>~ только патч-версия пакета.<br />
<em>То есть, если указано &#8220;~1.5.0&#8221;, это значит можно обновиться например до &#8220;1.5.1&#8221;, но до &#8220;1.6.0&#8221; уже нельзя.</em></p>
<p>^ только минорные версии и патчи.<br />
<em>Пишем: &#8220;^1.5.0&#8221;, можем обновиться до &#8220;1.5.1&#8221; или &#8220;1.6.0&#8221;. До &#8220;2.0.0&#8221; уже нельзя.</em></p>
<p>* любые апдейты пакета: мажор, минор и патч</p>
<p>&gt;, &gt;= любые апдейты пакета, выше или такой же версии</p>
<p>&lt;, &lt;= любые апдейты пакета, ниже или указанной версии</p>
<p>latest &#8211; самую свежую версию пакета</p>
<p>Просто номер версии пакета &#8211; в точности указанную версию пакета и никакую другую</p>
]]></content:encoded>
					
					<wfw:commentRss>http://dreamhelg.ru/2020/07/25/npm-packages-syntax-howto/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
