воскресенье, 23 февраля 2025 г.

PrimeNG. Dialog. Особенности Breakpoints

В компоненте Dialog из библиотеки PrimeNG есть возможность задавать ширину диалогового окна в зависимости от ширины окна браузера. Не просто там в процентах (хотя и это тоже), а  пошагово. Ну, условно, обычно ширина диалога допустим 40rem, а если окно браузера в ширину 576 пикселей  или меньше, то ширина диалога 100%. 

Это делается помощью свойства breakpoints, в которое передается объект, свойства которого - это значение максимальной ширины окна брайзера, а значение этого свойства - это как раз ширина диалогово окна при такой ширине. Также через style.width можно задать ширину по умочанию - по факту ту, которая будет, если окно в браузере превысит максимально указанную ширину.

Как-то сложно выходит на словах. Давайте кодом. Например:


<p-dialog
  [breakpoints]="{
    '768px': '100vw',
  }"
  [style]="{ width: '40rem'}"
>  
  
В данном случае пока ширина окна браузера будет меньше или равна 768 пикселей, диалоговое окно будет на всю ширину окна, а если ширина окна больше 768, ширина диалога будет 40rem. Внутри это реализовнао с помощью создания медиа стилей @media. В данном случае будет добавлен стиль:

@media screen and (max-width: 768px) {
    .p-dialog:not(.p-dialog-maximized) {
        width: 30rem !important;
    }
}
Там еще будет завязка на уникальный id диалога, чтобы на другие диалоги это не распространялось, но это не важно.


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

То есть, если есть 2 правила на ширину 960 и 1200, то применяется правило 1200, потому что @media с условием max-width применяется на все ширины до указанной в условии, то есть и для 960 тоже. Но если условие с 960 добавить после условия с 1200, то 960 переопределит условие 1200, естественно только для ширины 960 и менее.

То есть, вот так правильно:
<p-dialog
  [breakpoints]="{
    '1200px': '40rem',
    '960px': '30rem',
  }"
  [style]="{ width: '50rem'}"
>
А вот так неправильно:
<p-dialog
  [breakpoints]="{
    '960px': '30rem',
    '1200px': '40rem',
  }"
  [style]="{ width: '50rem'}"
>
Для наглядности сделал пример

суббота, 22 февраля 2025 г.

Bun ломает Angular Schematic в WebStorm|PhpStorm

Столкнулся я тут с каким-то Bun. Это типа замена NodeJs, почти полностью с ним совместимая, но быстрее. Посмотрел - вроде выглядит здорово. Начал "тыкать", все прекрасно работает и вдруг... 

вторник, 16 июля 2024 г.

NGINX и PHP-FPM. "Грабли" с doc_root

Вводные. 

Unix-like система. Стоит Nginx v1.25.3 и PHP-FPM v8.3.2
Теперь важно. В php.ini параметр doc_root = "/opt/share/www". 
Допустим наш сайт лежит в /opt/share/www/site1. Там есть index.php следующего содержания:
<?php echo __FILE__;
Ну, то есть, примитивизм - в случае обращение к этому файлу, он будет выдавать свой абсолютный путь.

Конфигурация nginx выглядит примерно так:

	location /api/ {
		root /opt/share/www/site1;
		default_type "application/json";
		autoindex off;

		fastcgi_pass unix:/opt/var/run/php8-fpm.sock;
		include fastcgi_params;
		
		fastcgi_split_path_info ^(/api)(/.+)$;
		fastcgi_param SCRIPT_FILENAME $document_root/index.php;
		fastcgi_param PATH_INFO $fastcgi_path_info;
		fastcgi_param REQUEST_URI $fastcgi_path_info;
	}

Так вот в такой ситуации при обращении по адресу http://site1/api/blablabla (подразумевается что site1 маппится на нужный для nginx IP и порт) вам в браузер будет выдаваться "No input file specified". При этом в error.log будет что-то типа "Unable to open primary script: /opt/share/www/site1/index.php (No such file or directory)".

Если заглянуть в nginx/fastcgi_params, то мы там кстати увидим, что никакой SCRIPT_FILENAME не задаётся, а задаётся SCRIPT_NAME. Может это его надо переопределить? 

Допишем его в конфиг nginx аналогично SCRIPT_FILENAME:

  
  include fastcgi_params;
  ...
  fastcgi_param SCRIPT_FILENAME $document_root/index.php;
  fastcgi_param SCRIPT_NAME $document_root/index.php;
  ...

Ну и, понятное дело, перезагрузим конфигурацию nginx через "nginx -s reload"

Это ничего не даст. Ошибка будет та же.

Если же убрать из конфига nginx передачу параметра SCRIPT_FILENAME, то ошибка будет другая: в браузере будет "File not found", а в error.log - "Primary script unknown".

Ну ОК. Вы скажете: "Чувак, у тебя задан doc_root, вот и надо указывать путь к index.php не абсолютный путь относительно корня, а от /opt/share/www !"

Ха, если бы все было так просто. Ну смотрите. Выкидываем $document_root из параметров и делаем так:

  
  include fastcgi_params;
  ...
  fastcgi_param SCRIPT_FILENAME /site1/index.php;
  fastcgi_param SCRIPT_NAME /site1/index.php;
  ...
Следуя логике, у нас /opt/share/www соединится с /site1/index.php, и мы получим абсолютный путь. 
Перечитываем конфигурацию nginx. 
Переходим по http://site1/api/blablabla. 
Получаем "Primary script unknown"... 

И как бы мы не извращались передавая только SCRIPT_FILENAME или только SCRIPT_NAME - ошибка та же. 

А теперь, собственно, в чём прикол? Ну и решение. 
SCRIPT_FILENAME - это параметр для php-fpm. И он НЕ зависит от doc_root в php.ini. 
SCRIPT_NAME - это параметр для php. И он зависит от doc_root в php.ini. 

Таким образом, правильной конфигурацией nginx будет:
  
  include fastcgi_params;
  ...
  fastcgi_param SCRIPT_FILENAME $document_root/index.php;
  fastcgi_param SCRIPT_NAME /site1/index.php;
  ...
В таком случае, в нашем примере, в конечном итоге оба параметра будут ссылаться на /opt/share/www/site1/index.php 

ЗЫ. Причем, что интересно, SCRIPT_FILENAME может указывать на другой php-файл (может быть даже не на php-файл - не проверял) - главное чтобы этот файл существовал. А выполнится при этом тот файл, который указан именно в SCRIPT_NAME. 

ЗЗЫ. Естественно, если просто закоментировать doc_root в php.ini, то первоначальная конфигурация будет работать.

понедельник, 17 сентября 2018 г.

PostgreSQL. Миграция базы на новую версию.

Для разработки локально я пользуюсь OpenServer. Периодически у него выходят новые версии, в которых добавляются новые версии модулей, в частности PostgreSQL. В принципе OpenServer тут не причём, и все ниженаписанное относится просто к переезду на новую версию PostgreSQL.

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

Собственно в чем проблема? Проблема в том, что разные версии PostgreSQL хранят данные в разных папках и тупо скопировать/переименовать старую папку в новую не получится - после такого новая версия просто не запускается.

Ок. Для этого есть стандартный приём - сделать дамп базы в старой версии и восстановить ее в новой.

По началу, чтобы не заморачиваться с консолькой и pg_dump, я решил заюзать что-то более дружелюбное.
Идущий в составе OpenServer последний официальный PhpPgAdmin не обновлялся много лет и с PHP7 не работает вообще. Есть работающий форк , но с экспортом через него тоже не вышло - данные-то он вроде выгружает, а вот структуру БД нет. Возможно в каком-то из 218 форков и это решено, но перебирать их все я не стал.

Далее я попробовал экспортировать БД с помощью HeidiSQL, но эта софтина мне вообще нифига не экспортировала, а только сыпала ошибками типа



/* ERROR:  syntax error at or near "CREATE" LINE 1: SHOW CREATE DATABASE "public"

Все таки поддержка PostgreSQL в ней заявлена как экспериментальная. Хотя сообщения о такой проблеме были много билдов назад, могли бы и пофиксить уже.

В итоге дамп я выгрузил с помощью pg_dump.exe, но пока я гуглил как же это все таки сделать правильно, я наткнулся на упоминание pg_upgrade  и решил попробовать ее.

Естественно с первого десятка раз нифига не получилось. Софтина неизменно писала в лог ошибку:


pg_ctl: unrecognized operation mode "50432"

В русской версии это звучит как "pg_ctl нераспознанный режим работы "50432"".

Собственно, софтина пытается запустить сервер PostgreSQL через pg_ctl на порту 50432 и у нее нифига не выходит. В логе pg_upgrade_server_start.log есть вся команда, которая пытается выполниться. Попытка выполнить запуск сервера этой же командой из консоли самостоятельно приводила к такому же результату. В команде в частности в параметре -o передаются параметры, которые будут переданы непосредственно исполнимому файлу postgres. Выглядел он так -o "-p 50432 -b ". То есть, теоретически, в postgres должен передаваться порт 50432.  Какой нафиг режим работы?!

Я пошел проанализировал исходники pg_ctl и у меня закралось подозрение, что pg_ctl как-то не так парсит переданные ей параметры. В итоге до меня дошло. Причина оказалась в следующем.

Вызов pg_upgrade производился такой командой:

pg_upgrade.exe
  -b e:\Soft\OpenServer\modules\database\PostgreSQL-9.6-x64\bin\ 
  -B e:\Soft\OpenServer\modules\database\PostgreSQL-10.5-x64\bin\
  -d e:\Soft\OpenServer\userdata\PostgreSQL-9.6-x64\ 
  -D e:\Soft\OpenServer\userdata\PostgreSQL-10.5-x64\

Тут -d  - это путь к базе старой версии. В pg_ctl это попадало так (лишнее опущено:)

pg_ctl -D "e:\Soft\OpenServer\userdata\PostgreSQL-9.6-x64\" -o "-p 50432 -b " start

Обратите внимание на последний слэш в пути, выделенный красным. Судя по всему, pg_ctl считает что \" это не окончание параметра пути, а экранированный символ кавычек, и строка пути продолжается дальше. В итоге она не находит знакомой команды (в данном случае start, так как start становится значением параметра -b) и возникает ошибка.

В итоге если убрать все оконечные слэши в путях все запускается и отрабатывает на ура. Единственное, еще нужно добавить параметр -U postgres для pg_upgrade (и возможно запускать от имени администратора). Таким образом, итоговая работающая команда выглядит так:

pg_upgrade.exe
  -b e:\Soft\OpenServer\modules\database\PostgreSQL-9.6-x64\bin 
  -B e:\Soft\OpenServer\modules\database\PostgreSQL-10.5-x64\bin
  -d e:\Soft\OpenServer\userdata\PostgreSQL-9.6-x64 
  -D e:\Soft\OpenServer\userdata\PostgreSQL-10.5-x64
  -U postgres  

вторник, 4 апреля 2017 г.

Angular 2. SystemJS для разработки, WebPack для продакшена. Проблемы с LazyLoading и относительными путями вообще


Введение 

Начинал я делать проект на каком-то там ReleaseCandidate Angular2 или даже на beta. И, соответственно, во всю там применялся SytemJS для подгрузки частей приложения. Сейчас команда Angular постепенно мигрирует на Webpack, но SystemJS еще пока есть в гайдах, полно примеров как делать именно с его помощью, короче - помирать пока не собирается. И, откровенно говоря, с ним удобнее разрабатывать.
Как выглядит разработка с использованием SystemJS? Вы правите свой ts-файлик, сохраняете, он компилируется в js-файлик. Ручками или, что удобнее, с помощью запущенного через npm typescript компилятора в режиме ожидания изменений, или, что еще удобнее, IDE вам сама как-то там запускает typescript-компилятор. Все это не столь важно. Важно что через секунду после сохранения ts-файла у вас новый js-файл. Можно в браузере нажать обновить и все - новый код в работе.
Кроме того, SystemJS по умолчанию грузит каждый файл отдельным запросом. Да - для продакшена это плохо, но для разработки это удобно.

Что происходит, если перейти на WebPack? После того как вы поменяли и сохранили свой ts-файлик, вам надо перебилдить bundle с помощью webpack. На большом проекте это время. Да, для webpack есть некий webpack-dev-server, который можно запустить в режиме отслеживания изменений, он будет на лету как-то там посылать измененные js прямо в браузер. Но у меня эта схема не заработала, подозреваю из-за того что у меня тут же крутится и backend на PHP. А вот для продакшена webpack классный - твои сотни js- и html- файлов собираются в несколько bundle.js (как настроишь, хоть все в один) и запросов на сервак уходит на порядки меньше. Причем там еще все это можно сжать, минифицировать и так далее. Таким образом работает все намного быстрее.

Возможно для SystemJS тоже есть некое подобие сборки всего в один-два файла. Я не разбирался и смысла не вижу на данном этапе.

Проблема

Я попробовал перевести проект на рельсы Webpack. После некоторых танцев с бубном, все получилось. Но выявилась проблема с лениво загружаемыми модулями. В Angular для таких в роутере указывается параметр loadChildren и его значение либо строка, либо функция типа LoadChildrenCallback:
export declare type LoadChildrenCallback = () => Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>>;
Для Webpack c этим ничего сложного нет - подключаете там нужный loader (e.g., angular-router-loader), и он делает так, что ваши LazyLoaded модули превращаются в отдельные xx.chunk.js и грузятся когда надо. Статей про это полно в инете полно. В конфиге webpack у меня например написано так:
...
module: {
        rules: [
            {
                test: /\.ts$/,
                loaders: ['awesome-typescript-loader', 'angular2-template-loader','angular-router-loader']
            },
  ...
}

Более того, в webpack пути в loadChildren указываются относительные, что удобнее и логичнее. А в том же пресловутом SystemJS, пути от корня приложения. И приходилось либо реально писать фактически абсолютные пути, либо изобретать велосипеды в виде собственного загрузчика ну или генератора абсолютного пути по относительному. Ну, например, типа таких: Первым параметром там передается moduleId. Вопрос откуда он берется. А это ключевая штука.Дело в том что в момент загрузки скрипта и его интерпретации поле module.id равно абсолютному пути этого скрипта. То есть если, скажем, ваши корневые маршруты лежат в app/app.route.ts, то в момент интерпретации app.route.js module.id будет равен "http://blabla.bla/app/app.route.js". Таким образом в app.route.ts можно написать что-то типа
loadChildren: Helpers.getAbsolutePath(module.id, './dash/dash.module#DashboardModule')
и в loadChildren будет абсолютный путь. Но все это не прокатывает для WebPack. Проблем а в том, что его loader такую конструкцию не поймет и наш lazy-loaded модуль просто не попадет в сборку. Это происходит поскольку загрузчик просто ищет строку в ts-файле по регулярному выражению и наши Helpers.blablabla ему ни о чем не говорят.

Решение

 Вообще можно, наверно, было форкнуть загрузчик webpack и заставлять его понимать такие извращения, но, во-первых, мне это показалось еще пока сложноватым, а во-вторых, больше хотелось заставить SystemJS понимать относительные пути.
И я начал ползать по дебрям Angular, Observable и Promise пытаясь понять где, как и в какой момент это все грузится и можно ли туда внедриться.
Грузится это все, кстати, классом RouterConfigLoader. На вход ему подается абстрактный NgModuleFactoryLoader, а реально там SystemJsNgModuleLoader.
RouterConfigLoader подменить не вышло (Angular его не экспортирует, проинжектить вместо него что-то другое тоже), подменять SystemJsNgModuleLoader смысла нет, потому что его методы на вход приходит уже просто строка пути. А идея была строку не трогать никак, чтобы была совместимость с webpack.
И я заметил что RouterConfigLoader перед загрузкой дергает callback onLoadStartListener и пошел искать откуда это берется и куда ведет. Оказалось, что это метод onLoadStart класса Router и передается он в RouterConfigLoader при его создании. Метод в свою очередь вызывает метод triggerEvent с новым экземпляром RouteConfigLoadStart, внутри которого маршрут route, который мы будем грузить. А triggerEvent просто вызывает next на routerEvents, которые есть Observable и на них можно подписаться через router.events.

И возникла следующая схема. В маршруты в поле data мы добавляем значение module.id. Таким образом, в момент интерпретации файла с маршрутами у нас в нем будет сохранен статический относительный путь от него до требуемого lazy-loading модуля и динамический абсолютный путь до самого фала маршрута. Далее в корневом (чтобы как можно раньше) компоненте app.component (который мы bootstrap'им) мы получаем экземпляр класса Router с помощью DI, подписываемся на его события и, если приходит RouteConfigLoadStart, то вычисляем абсолютный путь до модуля и подменяем его в маршруте.

Получился вот такой код:
export class AppComponent {
    constructor(private router: Router) {
        router.events.filter((event) => event instanceof RouteConfigLoadStart).subscribe((event: RouteConfigLoadStart) => {
            if (event.route && event.route.loadChildren && event.route.data && event.route.data.moduleId && !event.route.data.absolute) {
                if (typeof event.route.loadChildren === "string") {
                    let relPath = event.route.loadChildren;
                    if (relPath.substr(0, 1) == ".") {
                        event.route.loadChildren = Helpers.getAbsolutePath(event.route.data.moduleId, relPath);
                        event.route.data.absolute = true;
                    }
                }
            }
        })
    }
}

А в маршрутах пишем примерно так:
export const routes: Routes = [
    {
        path: '',
        loadChildren: './main/main.module#MainModule',
        data: {
            moduleId: module.id,
        }
    }

Тут я в route.data.absolute прописываю true, чтобы второй раз не срабатывало, но вообще загрузка происходит 1 раз и второго раза не будет для маршрута. Просто перестраховка.
Также я собирался проверять в runtime используем ли мы systemjs, но оказалось это не надо и под webpack тоже все работает чудесным образом.

понедельник, 3 апреля 2017 г.

О чем это...

Описания последствий от наступления на грабли при разработке сайтов.
Изобретения велосипедов и внедрение костылей.
Борьба с фреймворками.
Бэкэнд и фронтенд.

Этот блог - записки практически начинающего web-разработчика, который когда-то "видел", но последнее время занимался совсем другими проектами, а теперь пытается наверстать упущенное и стать в тренде. Но со своими тараканами. Особых откровений тут не будет, но и банальщины, надеюсь, тоже.