Показаны сообщения с ярлыком js. Показать все сообщения
Показаны сообщения с ярлыком js. Показать все сообщения

16 июн. 2025 г.

Debug CLS: List DOM elements, their coordinates, and area changes for every layout shift

(new PerformanceObserver((list) => {

    const entries = list.getEntriesByType('layout-shift');

    entries.forEach((/*LayoutShift*/entry) => {

        console.log(entry);

        const /*Array<LayoutShiftAttribution>*/sources = entry.sources || [];

        const changes = [];

        for (const source of sources) {

            const { currentRect: cr, previousRect: pr, node } = source;

            const dx = cr.x - pr.x;

            const dy = cr.y - pr.y;

            const ds = cr.width * cr.height - pr.width * pr.height;

            changes.push({dx, dy, ds, node});

        }

        if (changes.length) {

            console.table(changes);

        }

    });

})).observe({ type: 'layout-shift', buffered: true });


25 окт. 2023 г.

How to use ESLint + Prettier + VS Code together without conflicts

  1. Install eslint, prettier, eslint-plugin-prettier, eslint-config-prettier
  2. Update eslintrc file to look like this:

extends: [
  'eslint:recommended',
  'plugin:react/recommended', // if react is used
  'plugin:react-hooks/recommended', // if react is used
  'plugin:@typescript-eslint/recommended', // if ts
  'plugin:prettier/recommended'
],
plugins: ['prettier', 'react', 'react-hooks', '@typescript-eslint'],

With this setup ESLint starts to use Prettier and its rules for code style checks and autofixes.

Moreover, now you don't need two separate commands to run ESLint and Prettier, they both will run with a single command eslint --fix.

Additional steps for VS Code:

  1. Install the "Prettier - Code formatter" extension
  2. Go to Settings and set the "Editor: Default formatter" to "Prettier - Code formatter"
  3. Enable "Editor: Format On Save"

29 мар. 2022 г.

React.StrictMode calls your render() and reducer() twice

In a strict mode development build of React renders your components twice. It calls your render() method, functional components, all the hooks two times. The reducer() function from the useReducer() hook is also called twice.

Before the second call, React disables all console output methods. In case your code works in non-strict mode but shows strange results in strict mode, you may want to see console output from the second call. In order to do that you can store the original console.log method at the very beginning of your code:

import { useReducer } from "react";
// other imports...

const log = console.log;

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, undefined, init);
  log("MyComponent", state); // note: log instead of console.log
  return <div>markup...</div>;
}


3 авг. 2020 г.

Tools for performance analysis in Node.js

  • node-tick-processor easy-to-install processor for the v8 profiler log
  • node-gcstats exposes stats about V8 GC after it has been executed
  • 0x single-command flamegraph profiling
  • autocannon HTTP/1.1 benchmarking tool written in node, with support for HTTP pipelining and HTTPS
  • k6.io open source load testing tool and SaaS for engineering teams
  • why-did-you-render monkey patches React to notify you about avoidable re-renders (works with React Native as well)

10 апр. 2020 г.

19 мар. 2020 г.

Профилирование скорости server-side рендеринга (SSR) компонентов на React + TypeScript

Подготовка

Для запуска ts tsx файлов в Node.js устанавливаем пакет ts-node:

npm i ts-node

Чтобы при запуске игнорировать импорт стилей в файле компонента (который может выглядеть примерно так: import './MyComponent.scss';), устанавливаем пакет ignore-styles:

npm i ignore-styles

Создание файла с бенчмарком

Создаём tsx файл, в котором импортируем наш компонент MyComponent, задаём props и вызываем renderToString():

import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { MyComponent } from './MyComponent';

const props = {/* ... */};
console.log(renderToString(<MyComponent {...props} />));

Запускаем его, чтобы убедиться, что всё работает без ошибок, компонент рендерится и в консоль выводится правильная разметка компонента:

NODE_ENV=production node -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Если не хватит памяти, надо добавить параметр --max_old_space_size=4096

NODE_ENV=production node --max_old_space_size=4096 -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Если памяти всё равно не хватит - надо поставить вместо 4096 число побольше. Запоминаем подобранные параметры.

Убедившись, что всё работает, в файле бенчмарка убираем вывод в консоль и дописываем вызов renderToString() в цикле:

import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { MyComponent } from './MyComponent';

const props = {/* ... */};

for (let i = 0; i < 1000; i++) {
    renderToString(<MyComponent {...props} />);
}

Запуск бенчмарка

Запускаем бенчмарк (в подобранную ранее командную строку добавляется параметр --prof):

NODE_ENV=production node --prof -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

или

NODE_ENV=production node --prof --max_old_space_size=4096 -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Первый запуск после изменения файла будет долгим и даст совсем неправильные результаты, т.к. под капотом компилируется TypeScript, и процесс компиляции тоже попадёт в собранный профиль, что нам не нужно. Поэтому после каждого редактирования запускаем бенчмарк по два раза, второй раз он выполнится быстрее.

После каждого запуска должны создаваться файлы isolate*.log.

Теперь вместо числа 1000 надо подобрать такое, чтобы второй запуск бенчмарка занимал достаточно продолжительное время (десятки секунд): меняем число, запускаем два раза, оцениваем продолжительность второго запуска.

После этого удаляем все накопившиеся файлы isolate*.log, запускаем бенчмарк ещё раз - мы должны получить ровно один файл isolate*.log.

Обработка и анализ собранного профиля

Полученный файл isolate*.log надо обработать, чтобы получить на выходе читабельный профиль выполнения:

node --prof-process isolate-0x104000800-v8.log >isolate-0x104000800-v8.txt

Вместо isolate-0x104000800-v8 надо подставить своё имя файла. Также для обработки лога можно использовать отдельный пакет https://www.npmjs.com/package/tick-processor, часто он лучше обрабатывает лог и не теряет данные, в обличие от встроенного в саму ноду процессора логов:

tick-processor isolate-0x104000800-v8.log >isolate-0x104000800-v8.txt

В текстовом файле мы видим, какие именно методы v8 и нативного C++ кода вызывались и сколько времени заняли. Подробное описание содержимого и методов его анализа - тема, достойная отдельной большой статьи.

1 окт. 2018 г.

Визуализация результатов профилирования в chrome://tracing

При профилировании сложных библиотек и фреймворков типа React и Angular в полученных результатах очень много визуального шума от всевозможных обёрток и утилитных функций, которые загромождают стек. Можно и нужно сделать свой собственный код для более высокоуровневого профилирования (например, в React замерять только времена стандартных методов жизненного цикла компонента: shouldComponentUpdate, render и т.д.).

Встаёт вопрос визуализации полученных данных. Таблички - хорошо, но таймлайн или flame chart - нагляднее и легче для анализа. Можно быстро набросать визуализатор с помощью Google Charts Timeline, но хочется чего-то более мощного и интерактивного, как в Dev tools, с возможностью масштабирования и перемещения вдоль временной оси.

1 июл. 2018 г.

24 янв. 2018 г.

Don't forget to test your web site/application in private browsing (incognito) mode

From time to time one or another web site or web application (SPA, PWA whatever word is now trendy) faces that problem: it throws errors and does not work when private browsing mode is turned on. Real-world examples: Google+ signinAmazon Cognito Identity SDK.

Here is more-or-less correct and complete list of things that don't work when private browsing (incognito mode) is turned on:
  • Safari 10 and below: localStorage and sessionStorage API is present, but throws a DOMException.QUOTA_EXCEEDED_ERR when the code is trying to write something. Fixed in Safari 11
  • IE10+/Edge: indexedDB is undefined
  • Firefox: indexedDB is undefined
A similar situation may happen when the user has disabled cookies/data, e.g. in Chrome/Chromium: Settings -> "Show advanced settings..." -> "Content settings..." -> "Block sites from setting any data".
  • Chrome/Chromium: cookies, localStorage, sessionStorage and indexedDB are disabled. Will throw "Uncaught SecurityError: Access to 'localStorage' is denied for this document".
  • Firefox: cookies, localStorage, sessionStorage
  • IE: cookies
And one more restriction exists in Safari when JavaScript is in strict mode: modification of error object may be forbidden (both overwriting existing property and adding a new one) and may throw

15 авг. 2017 г.

EcmaScript support in Node

Interestingly enough, even 9.0.0 nightlies don't support ES2015 fully: it's missing tail call optimization, Array.prototype.values, and RegExp.prototype.flags.
http://node.green/ - ES2015 ES2016 ES2017 compatibility tables for Node.

List all the in progress features available on your Node release:
node --v8-options | grep "in progress"

List all dependencies and respective versions that ship with a specific Node binary:
node -p process.versions.v8
https://nodejs.org/en/docs/es6/

7 авг. 2017 г.

Get Pseudo-Element Properties with JavaScript

Assume your CSS looks like:

.element:before {
    content: 'NEW';
    color: rgb(255, 0, 0);
}

To retrieve the color property of the .element:before, you could use the following JavaScript:

var color = window
    .getComputedStyle(document.querySelector('.element'), ':before')
    .getPropertyValue('color')

Passing the pseudo-element as the second argument to window.getComputedStyle allows access to said pseudo-element styles


27 июл. 2017 г.

All your comparators are belong to us :)

Some implementation details of third-party code could be determined by fingerprinting how that code calls handlers/callbacks we supply to it:

https://habrahabr.ru/post/303748/
https://siri0n.github.io/array_sort_fingerprint/

6 мая 2017 г.

How to properly define propTypes for React component

Wrong way

// Wrong! propTypes should be either static property or static property getter
class TextWrongES6 extends Component {
    static propTypes() {
        return { children: PropTypes.string };
    }

    static defaultProps() {
        return { children: 'Hello World!' };
    }

    render() {
        return <p>{this.props.children}</p>;
    }
}

Right ways

// The ES5 way
var TextES5Way = React.createClass({
    propTypes: { children: PropTypes.string },

    getDefaultProps: function() {
        return { children: 'Hello World!' };
    },

    render: function() {
        return <p>{this.props.children}</p>;
    }
});

// The ES6 way - ES6 class + class properties
class TextES6Way1 extends Component {
    render() {
        return <p>{this.props.children}</p>;
    }
}
TextES6Way1.propTypes = { children: PropTypes.string };
TextES6Way1.defaultProps = { children: 'Hello World!' };

// The ES6 way - ES6 class + ES5 getters
class TextES6Way2 extends Component {
    static get propTypes() {
        return { children: PropTypes.string };
    }

    static get defaultProps() {
        return { children: 'Hello World!' };
    }

    render() {
        return <p>{this.props.children}</p>;
    }
}

// The ES7 way - static property initializers (experimental feature)
class TextES7Way extends Component {
    static propTypes = { children: PropTypes.string };
    static defaultProps = { children: 'Hello World!' };

    render() {
        return <p>{this.props.children}</p>;
    }
}

// The Stateless Functional Component way
const Text = (props) => <p>{props.children}</p>;
Text.propTypes = { children: PropTypes.string };
Text.defaultProps = { children: 'Hello World!' };