Философия программирования
192 subscribers
17 photos
33 links
Frontend без воды: говорим о технологиях, архитектуре, принципах, кодстайле и том, как превращать хаос в систему.
Download Telegram
Channel name was changed to «Что-то на программистском»
Ещё один блог про frontend разработку.

Важное замечание: не стоит воспринимать написанное здесь как претензию на истину, это лишь личное мнение в конкретный период времени. Плюс, из каждого правила могут быть исключения.

Материалы доступны под лицензией CC-BY-NC-ND 4.0 International.
Философия программирования pinned «Ещё один блог про frontend разработку. Важное замечание: не стоит воспринимать написанное здесь как претензию на истину, это лишь личное мнение в конкретный период времени. Плюс, из каждого правила могут быть исключения. Материалы доступны под лицензией…»
Как ускорить Webpack сборку проекта на Typescript с 42 до 16 секунд

При переходе на очередной проект, столкнулся с тем, что его локальный запуск происходит не быстро. Репозиторий не сказать бы что большой, поэтому 42 секунды (mac air m1 2020) для него многовато. Итак, вот один из примеров, что можно попробовать сделать в подобном случае:

Для начала, нужно понять на что именно уходит это время. Для любого инструмента сборки есть или встроенные средства, или стороннее расширение. В нашем случае, это speed-measure-webpack-plugin. Устанавливаем, оборачиваем им наш конфиг сборки, запускаем и видим следующее:


General output time took 42.15 secs
eslint-webpack-plugin took 21.59 secs
awesome-typescript-loader took 12.51 secs


На самом деле список результатов длиннее, но я показываю лишь то, что нам сейчас интересно.

Итак, половина времени уходит на ESLint проверки, ну и наш typescript код собирается не сказать бы что быстро.

Первым делом идём за описанием настроек eslint-webpack-plugin и ищем параметры которые помогут сократить время проверки. threads: true - кажется то что нужно, и оно действительно ускоряет проверку в разы, но вот незадача, ошибки и предупреждения перестают выводиться в консоль. Ковыряния в issues приводит к тому что это баг и похоже его ни кто не собирается чинить. Попутно находится issue, в котором пишут что скорость eslint-webpack-plugin оставляет желать лучшего, и с этим тоже ни чего особого не делают.

Пока отложим этот вопрос и перейдём к сборке typescript кода.

Идём в описание awesome-typescript-loader и видим раздел performance issues, читам, понимаем что в нашем случае всё уже сделано. Остаётся последовать совету, и попробовать ts-loader. Заменяем awesome-typescript-loader на ts-loader и видим следующее:


ts-loader took 12.17 secs


На первый взгляд, кажется что мы поменяли шило на мыло, но не расстраиваемся и идём в раздел документации faster builds и пробуем что там написано. В итоге приходим к следующему:




loader: 'ts-loader',
options: {
transpileOnly: true,
onlyCompileBundledFiles: true,
}


Плюс, устанавливаем и настраиваем fork-ts-checker-webpack-plugin, что в итоге даёт нам следующее:


fork-ts-checker-webpack-plugin took 0.002 secs
ts-loader took 8.3 secs // <= неплохое улучшение


Если поковырять описание, то находим следующее: ESLint feature, please use version 6 of the plugin. Не смотря на то, что уже есть 8 версия, 6-ю продолжают обновлять (по крайней мере на момент написания этого текста). Разбираемся что за ESLint фича такая, удаляем eslint-webpack-plugin, а вместо него включаем ту самую фичу:




new ForkTsCheckerWebpackPlugin({
eslint: {
// Тут желательно использовать переменную,
// что бы true был только при локальной разработке
enabled: true,
// Файлы, которые необходимо проверять
files: './src/**/*.{ts,tsx}',
},
}),


В итоге мы получаем следующее:


General output time took 15.77 secs
fork-ts-checker-webpack-plugin took 0.002 secs
ts-loader took 8.3 secs


Судя по времени выполнения, кажется что fork-ts-checker-webpack-plugin делает все проверки асинхронно, что позволяет стартануть проект очень быстро.

Кстати, по умолчанию, все ESLint ошибки и предупреждения сыпятся не только в системную консоль (где выполняли команду запуска), но и в консоль браузера.

На самом деле сборку можно было бы ещё ускорить до ~10 сек, заменив ts-loader на esbuild-loader (который к тому же поддерживает минификацию и css), но конкретно в моём случае пока что нужно сохранить сборку в target es5.
2
Пара нюансов в вопросе Webpack vs Vite

На первый взгляд может показаться что одно можно легко заменить на другое, ну или как минимум не трогать код проекта, но это не так. Я столкнулся с двумя моментами, но скорее всего их больше.

1. Обработка ошибок загрузки (не существующих) чанков

Если ваша сборка подразумевает что имена чанков между сборками могут отличаться, в хэш части, то хорошей практикой будет добавить обработку ошибки загрузки не существующих файлов. Webpack, в данном случае, возвращает специализированные ошибки:


Для JS: error.name = ChunkLoadError
Для CSS: error.code = CSS_CHUNK_LOAD_FAILED


Vite же, для js вернёт обычную ошибку, сообщение в которой может варьироваться в зависимости от браузера, а для css — заготовленную ошибку Unable to preload CSS.

Про обработку таких ошибок расскажу как нибудь в другой раз.

2. Ручное именование чанков

Иногда возникает потребность в более детальной настройке того, что и как попадёт в конкретный чанк. Для этого, в webpack, есть так называемые магические комментарии https://webpack.js.org/api/module-methods/#magic-comments. В данный момент нас интересует webpackChunkName, который позволяет указать название чанка (то, как будет именоваться js файл).

Естественно, подобные комментарии не работают в Vite. Но, мы не первые, кто столкнулся с этим вопросом, поэтому уже есть плагин https://www.npmjs.com/package/vite-plugin-webpackchunkname. Лично пока не пробовал, могут быть нюансы.

#development #frontend #javascript #webpack #vite
Экспериментальная оптимизация Webpack

Как сократить время локального запуска проекта, например, с 16 до 1 секунды? Если вы используете Webpack 5.17+, можно отложить сборку конкретного кода (чанка) до момента пока он не понадобится:

experiments: {
// true должно быть только при локальном запуске
lazyCompilation: true,
},

В итоге, чем лучше код разделён на чанки, тем меньше времени придётся ждать.

#development #frontend #webpack #optimisation #javascript

https://webpack.js.org/configuration/experiments/#experimentslazycompilation
Как в Webstorm найти не используемый код?

Заходим в меню Code > Analyze Code > Run Inspection By Name, в появившемся поле вводим “unused” и из списка подсказок выбираем Unused global symbol. Ну а далее настраиваем параметры и нажимаем ОК.

#development #frontend #javascript #webstorm
Про код-стайл, зачем он нужен и как должен выглядеть

Код-стайл (он же code style, coding standards, coding convention или programming style) — некоторый набор правил и соглашений для написания кода, над которым работает более одного разработчика.

Зачем нужны эти правила?

Первое, и самое важное — убрать разногласия между разработчиками, т.к. каждый привык писать по своему и считает что именно его вариант самый верный.
Второе — предотвратить появление распространённых ошибок в коде. Например then() написали, а catch() забыли и в итоге у нас ошибка на проде.
Третье — упростить чтение кодовой базы.

В каком виде эти правила и соглашения должны быть реализованы?

Самый эффективный способ — настроить автоматические инструменты для максимального количества правил. При этом, что бы все стилистические правила (табы vs пробелы, длинна строки и т.п.) применялись автоматически, например на pre-commit hook! Почему именно так? Да всё просто, всё что делается автоматом, не должно мешать во время написания кода. Важно отметить, что автоматическое применение стилей не должно происходить до момента пока код не дописан, т.к. есть правила которые удаляют не используемый код.

Из этого вытекает что наборов правил должно быть как минимум два:
первый — для этапа написания кода, что бы “по живому” показывать ошибки и предупреждения,
второй — для автоматического форматирования кода.

Скорее всего, останутся соглашения, которые невозможно реализовать через инструменты. Самое важное, что можно и нужно с ними сделать, помимо записи в общедоступном месте, постараться максимально сократить их количество, а те что остались — подробно описать.

Самые популярные инструменты, на данный момент:

https://eslint.org
https://prettier.io

#codestyle #eslint #prettier #frontend #development #javascript
Typescript: Union типы на практике

Представим, что мы пишем свою реализацию “хранилища” данных, которое должно возвращать примерно такое состояние:


interface State<T = unknown> {
status: 'loading' | 'error' | 'success',
error: Error | undefined,
data: T | undefined,
}


На первый взгляд может показаться что всё ок, однако это не совсем так. К примеру, проверка status ничего не скажет нам о состоянии других полей:


if (state.status === 'success') {
console.log(state.data.toFixed());
// Получаем ошибку
// 'state.data' is possibly 'undefined'.
}


Решаем проблему через union тип, добавляя три (по количеству состояний) отдельных интерфейса в каждом из которых error и data напрямую зависят от значения status:


interface LoadingState {
status: 'loading';
error: undefined;
data: undefined;
}

interface ErrorState {
status: 'error';
error: Error;
data: undefined;
}

interface SuccessState<T = unknown> {
status: 'success';
error: undefined;
data: T;
}

type State<T = unknown> =
| LoadingState
| ErrorState
| SuccessState<T>;


Проверяем наш код, и видим что всё работает без ошибок 🎉:


if (state.status === 'success') {
console.log(state.data.toFixed());
}

if (state.status === 'error') {
console.log(state.error.message);
}


Кстати, тоже самое можно провернуть и с типизацией props для React компонента, который должен принимать разный набор параметров в зависимости от значения одного общего.


type GridProps = SharedProps & (GridTypeProps | FlexTypeProps);


#typescript #frontend #development #react
👾1
Про чанки

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

Стоит сделать важное замечание: даже если нужно выполнить какую-то небольшую часть кода, загрузить и проанализировать браузер его должен полностью.

Предположим у нас есть веб-приложение с 10 страницами, из которых пользователи используют обычно 2-3, а к остальным обращаются редко, а то и вообще никогда. Будет странно загружать весь код (для всех страниц) одним куском (файлом).

Для решения этого вопроса и придумали разделение кода на небольшие кусочки (или чанки). В результате браузер загружает, анализирует и выполняет только тот код, который действительно нужен.

Разделение кода — не (совсем) автоматический процесс и требует осознанного подхода. Обычно, в отдельный чанк выносится та часть кода (со всеми её зависимостями), которая была использована через динамический импорт:


import('./something')


Но то, как именно код будет разделён, в конечном счёте зависит от настроек инструментов сборки, которые вы используете.

Как именно делить код, вопрос индивидуальный и зависит от конкретной задачи. Однако можно выделить пару универсальных советов. Во-первых, старайтесь выносить каждую страницу в отдельный чанк. Во-вторых, выносите функционал который не доступен на странице по умолчанию, без дополнительных действий пользователя.

Кстати, если вы пишите приложение с использованием React, то встроенная функция lazy и компонент Suspense должны быть вам хорошо знакомы.

#frontend #development #codesplitting #javascript #react
Архитектурная ошибка модальных окон в React

Один из примеров того, как не нужно делать в React.

Есть некий компонент в котором через меню открывается одно из модальных окон:


function Comp() {
return (
<>
<Menu /* выбор какое окно открыть */ />
<ModalA isOpen={modal === 'a'} />
<ModalB isOpen={modal === 'b'} />
<ModalC isOpen={modal === 'c'} />
</>
);
}


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

Дополнительной проблемой является то, что при закрытии такой формы, её состояние не сбрасывается, т.к. компонент продолжает “существовать”.

Решается это через условный render, ну или использованием функции по типу:


ui.openModal(<ModalA />)


Плюсом, можно обернуть каждый компонент в lazy. В таком случае, по “запросу” будут загружаться не только необходимые данные, но и сам код компонента.

#react #frontend #development #architecture
Паттерн switch(true)

Интересный паттерн который однако встречается достаточно редко. Настолько, что его полноценная поддержка в typescript появилась только в версии 5.3 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-3.html#switch-true-narrowing

Если в коде видим большое количество else if условий, например:


if (/* condition #1 */) {
// code #1
} else if (/* condition #2 */) {
// code #2
} else if (/* condition #3 */) {
// code #3
} else {
// code #4
}


Возможно, это то самое место, где можно и нужно применить switch(true):


switch(true) {
case /* condition #1 */:
// code #1
break;
case /* condition #2 */:
// code #2
break;
case /* condition #3 */:
// code #3
break;
default:
// code #4
}


Конечно это субъективно, но на мой взгляд, так выглядит понятнее и самое главное логичнее.

Кстати, в SolidJS есть компоненты которые используют этот паттерн:


<Switch fallback={<div>Not Found</div>}>
<Match when={state.route === "home"}>
<Home />
</Match>
<Match when={state.route === "settings"}>
<Settings />
</Match>
</Switch>


#javascript #typescript #frontend #development #patterns #react #solidjs