Все записи с тэгом dev

Путь к освоению VIM: от плагина до Practical VIM

Решил разобраться в VIM. Вообще я часто пользуюсь им, для быстрого редактирования файлов. Этакая разовая работа. Но полноценно переключиться на работу с VIM у меня не получается. По умолчанию использую VS Code + VIM плагин. Получается неплохой симбиоз: удобные хоткеи VS Code + удобная навигация внутри файла. Но одно дело удалять строчки и прыгать туда-сюда по файлы, а другое дело полноценно работать с большим проектом.

Обычно многие статьи заканчиваются командами h, j, k, l. А дальше читайте официальную документацию, там всё подробно описано. А выйти-то как? А как отступ добавить?

Так вот, отступ в режиме NORMAL задаётся через двойное нажатие >. И тут вот какая проблема: статьи-то на самом деле хорошие, просто команд и сценариев так много, что всего не перечислишь. А еще есть куча разнообразных форков со своими доработками. Например, я пользовался LunarVim. В целом, было неплохо, но некоторые вещи долгое время не исправлялись. И вот однажды я полез искать как же поправить один баг и с удивлением обнаружил, что проект не развивается.

Решил попробовать Astronvim. Проект оказался поинтереснее: быстрее, более структурированнее. И как-то так увлекся настройкой (что-то добавил, что-то переназначил), что в итоге сделал свой форк.

В какой-то момент, устав читать документацию и статьи, я купил себе книжку Practical VIM. Сначала читал демо, потом еще раз читал демо. Понял, что очень понравилось. Простой английский, легкое повествование. Текст читается прекрасно. К слову, у меня есть ещё одна книга про VIM “Изучаем vi и VIM”. Так вот в этой книге я продвинулся всего на четыре главы. В общем, “Practical VIM” понравилась больше.

И вот к примеру пара цитат из книги:

The combination of operators with motions forms a kind of grammar. The first rule is simple: an action is composed from an operator followed by a motion. Learning new motions and operators is like learning the vocabulary of Vim. If we follow the simple grammar rules, we can express more ideas as our vocabulary grows.
Neil, Drew. Practical Vim (pp. 24-25). Pragmatic Bookshelf. Kindle Edition.

и

when an operator command is invoked in duplicate, it acts upon the current line. So dd deletes the current line, while >> indents it.
Neil, Drew. Practical Vim (p. 25). Pragmatic Bookshelf. Kindle Edition.

Ещё книга содержит много отсылок к документации. При желании можно пойти и узнать больше деталей. В общем, книга классная, рекомендую.

Что касается VIM и ежедневного его использования, могу сказать, что в процессе чтения книги снова появился живой интерес к использованию. Некоторые вещи уже не выглядят болью. Еще один важный момент — готовый настроенный конфиг. Возможно, когда-нибудь я попробую всё настроить с нуля. Но вот прямо сейчас хочется использовать то, что работает из коробки. Поэтому решения вроде AstroNvim хорошо подходят.

Пробовал несколько раз Helix Editor, но там слишком много хотекеев переделано по-своему. Ребята хотели сделать понятнее, а получилось запутанее. Потому что сложно переучиться на другие команды и хотекеи.

Вывода у статьи не будет. Скорее это просто чекпоинт на тему “вот узучаю VIM”.

Тесты на Playwriter

Немного про разработку. Работал тут с Playwright. До этого писал тесты на Cypress. И после двух лет на Cypress кажется, что вдохнул свежего воздуха. При параллельном выполнении тестов Playwriter прогоняет их быстрее. А еще он запускает отдельный процесс для управления браузером, вместо выполнения тестов непосредственно в браузере.

В результате, переписал тесты к блогу на Playwriter и настроил Github Action.

20241014_082200_e4c38c.webp

Из интересного: для запуска тестов Nuxt + Playwriter нужно в playwright.config.js указать reuseExistingServer: true.

export default defineConfig({
...
webServer: {
command: 'npm run preview',
url: 'http://localhost:3000',
reuseExistingServer: true
}
}

Отладка через git bisect

Сегодня узнал о способе отладки через git bisect. Работает это путем поиска по истории репозитория.

Допустим зарелизили в прод стопицот фич и что-то сломалось. Неделю назад работало, а теперь нет. Берем коммит недельной давности, когда точно все работало и переключаемся в него: git checkout <тот самый коммит>. Ставим метку git bisect start.

Проверяем, все ли работает и если да ставим метку: git bisect good. Далее git кидает нас на следующий коммит. Если все работает ставим good, если нет: git bisect bad. И так продолжаем искать с середины диапазона сужая поиск пока не останется один коммит.

Найдя коммит, где все сломалось, уже можно понять проблему. Для завершения поиска: git bisect reset. Эта штука сегодня сильно помогла мне найти проблему, так что я решил записать все, пока не забыл 🙂

Подробнее можно почитать вот тут.

Немного git-а

git branch --contains <commit> – позволяет посмотреть в какой ветке коммит. А чтобы найти все ветки, понадобится флаг -a:

git branch -a --contains <commit>

При необходимости можно просто перейти на коммит: git checkout <commit>. Или вынести его в новую ветку: git checkout -b new-branch-name <commit>.

Подборка ссылок #02

— Вышел Neovim 0.10.
— Обзор нововведений в браузерах. Поддержка новых анимаций, нативные поповеры. Ну и ждём кастомные селекты. The latest in Web UI (Google I/O ‘24)
— Статья описывает концепцию рецептов в дизайн системе
— Отличное видео для новичков: React JS c Нуля – Курс для начинающих БЕЗ ВОДЫ
— Если вы помните, что когда-то был такой Wunderlist (который потом Microsoft купил), то знайте: разработчики вандерлиста выпустили новое приложение — Superlist. Выглядит красиво, пользоваться приятно, как и раньше.

Настройка VSCode

20241014_084139_87417a.webp

У меня VS Code выглядит довольно аскетично. Простая белая тема, минимум настроек. Иногда переключаюсь на темную тему, но рано или поздно возвращаюсь к белой.

Шрифт Fira Code.

Плагины must have для работы с Vue: Vue(Volar, не Vetur), ESLint, Prettier, GraphQL.

Еще дополнительно у меня установлены:

— эмулятор VIM
Better Comments
Color Highlight
EditorConfig
TODO Highlight

Файл .vscode/settings.json содержит вот такой конфиг:

{
// prettier
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
// eslint
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
// to see functions arguments name (optional)
"javascript.inlayHints.parameterNames.enabled": "all",
// quick suggestions while typing (optional)
"editor.quickSuggestions": {
"strings": true
}
// Common settings
"workbench.statusBar.visible": false,
"workbench.sideBar.location": "right",
"editor.fontSize": 13,
"editor.fontFamily": "Fira Code",
"editor.fontLigatures": true,
"editor.tabSize": 2,
"editor.fontWeight": "500",
"search.showLineNumbers": true,
// For VIM
"editor.lineNumbers": "relative",
"editor.rulers": [100],
"workbench.iconTheme": "vscode-icons",
"workbench.activityBar.location": "hidden",
"telemetry.telemetryLevel": "off",
"explorer.compactFolders": false,
"workbench.editor.showTabs": "none",
"breadcrumbs.enabled": false,
}

JavaScript. Взаимодействие с сервером

Примечание: Это статья — конспект моей лекции. В ней описываются ключевые понятия.

Обновление: В примерах ниже обновились url, в результате запросы по http не работают. Поэтому я внес небольшие корректировки и при получении url картинок заменяю http на https. Работающий код можно посмотреть тут https://codepen.io/denisfl/pen/xxeQzpp.

Чтобы взаимодействовать с сервером (запрашивать или отправлять данные), нам нужны методы. Каждый запрос, который мы отправляем на сервер, включает в себя endpoint и тип отправляемого запроса. Endpoint — это, своего рода, шлюз между клиентом и сервером. В зависимости от валидности запроса сервер отправляет ответ. Если запрос успешен, сервер возвращает данные, например, в формате JSON. В случае ошибки, сервер возвращает сообщение об ошибки. Ответы сервера обычно сопровождаются кодами состояния (status codes), которые помогают понять, что сервер пытается сказать при получении запроса.

Например: 200–299 — успешный запрос, 400–499 — ошибка клиента, 500–599 — ошибка сервера. Вот тут можно посмотреть полный список кодов: HTTP response status codes.

20241014_084402_ae1e5a.webp

Методы для отправки HTTP-запросов

GET. С помощью него клиент запрашивает у сервера содержимое ресурса.

HEAD. Это метод для получения заголовков ресурса. Обычно применяется для получения метаданных и проверки менялся ли ресурс с момента последнего посещения и существует ли он.

POST. Через этот метод клиент может передать данные в теле сообщения. Это могут быть какие-то данные с заполненной формы. POST является неидемпотентным методом, то есть при его отправке результат может отличаться. Кроме того, ответы на него не будут кэшироваться.

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

PUT. Метод создаёт новый ресурс или заменяет существующий данными, которые указаны в теле запроса.

PATCH. Работает таким же образом, как и PUT, только по отношению к части ресурса.

DELETE. Клиент сообщает о том, что хотел бы удалить некий ресурс.

TRACE. С помощью него можно проверить, изменяют ли промежуточные узлы в сети запрос клиента.

CONNECT. Запускает туннель между клиентом и сервером.

Как запросить данные

XMLHttpRequest. Это довольно старый метод, тем не менее он все еще используется.

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();

Axios. Это клиент созданный на Промисах, был создан для удобства работы с HTTP-запросами.

axios.get('https://api.example.com/data')
.then(response => console.log(response.data))
.catch(error => console.error('Ошибка:', error));

Fetch API. Это функция JavaScript, которую можно использовать для отправки запроса на любой URL-адрес веб-API и получения ответа.

fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка:', error));

Fetch и Axios очень похожи по функциональности. Axios создавался в тот момент, когда Fetch API не поддерживалось в разных браузерах и нужно было обеспечить совместимость с Internet Explorer. Сейчас можно использовать и то и другое, в зависимости от того, с чем комфортнее работать.

Запросили, получили, показали

Обычно если мы что-то запросили с сервера через GET, мы хотим это показать. В самом простом случае, мы вставляем полученные данные в DOM. Тут мы не говорим о современных решениях для работы с Virtual DOM.

Возьмем данные с http://pets-v2.dev-apis.com/pets и отрендерим их на страницы.

20241014_084426_8b3254.webp

Для запроса используем fetch api:

async function fetchData() {
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
// Допустим, у вас есть элемент в DOM для вставки данных
const container = document.getElementById('pets-container');
// Очищаем контейнер перед вставкой новых данных
container.innerHTML = '';
// Вставляем данные в DOM
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.textContent = `Имя: ${pet.name}, Вид: ${pet.animal}`;
container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
}
}
// Вызываем функцию для загрузки данных
fetchData();

И применим этот код к html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pets Data</title>
<style>
.pet {
margin-bottom: 20px;
border: 1px solid #ccc;
padding: 10px;
}
.pet img {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<div id="loading-message">Загрузка данных...</div>
<div id="pets-container"></div>
<script>
async function fetchData() {
const loadingMessage = document.getElementById('loading-message');
const container = document.getElementById('pets-container');
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
loadingMessage.style.display = 'none'; // Скрываем сообщение о загрузке
// Очищаем контейнер перед вставкой новых данных
container.innerHTML = '';
// Вставляем данные каждого питомца в DOM
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.classList.add('pet');
// Вставляем изображения питомца
pet.images.forEach(imageUrl => {
const img = document.createElement('img');
img.src = imageUrl;
petElement.appendChild(img);
});
// Вставляем информацию о питомце
const info = document.createElement('div');
info.innerHTML = `
<p><strong>Имя:</strong> ${pet.name}</p>
<p><strong>Вид:</strong> ${pet.animal}</p>
<p><strong>Город:</strong> ${pet.city}</p>
<p><strong>Штат:</strong> ${pet.state}</p>
<p><strong>Описание:</strong> ${pet.description}</p>
<p><strong>Порода:</strong> ${pet.breed}</p>
`;
petElement.appendChild(info);
container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
loadingMessage.textContent = 'Ошибка загрузки данных';
}
}
// Вызываем функцию для загрузки данных
fetchData();
</script>
</body>
</html>

В JavaScript функции являются объектами, и они могут иметь доступ к своему лексическому окружению, включая переменные и другие функции, определенные в том же контексте. В нашем случае, функция fetchData – это асинхронная функция, которая создает свое собственное лексическое окружение, когда она вызывается. Это лексическое окружение включает в себя все переменные и функции, определенные внутри нее, а также доступ к внешнему контексту, где она была объявлена.

Внутри функции fetchData мы обращаемся к внешним переменным, таким как loadingMessage и container. Эти переменные были определены в том же контексте, где и функция fetchData, и, следовательно, они доступны ей как внешние переменные.

Функция fetchData использует доступ к DOM-элементам loadingMessage и container, чтобы изменять их содержимое и стили. Это возможно благодаря замыканию, которое сохраняет ссылку на лексическое окружение, в котором была определена функция fetchData.

Внутри цикла forEach создается замыкание для каждой итерации. Это происходит потому, что каждый раз, когда вызывается метод appendChild() или устанавливается свойство innerHTML, создается новая функция, которая сохраняет ссылку на лексическое окружение функции fetchData.

Таким образом, замыкания в JavaScript позволяют функциям сохранять доступ к внешним переменным и контексту, в котором они были определены, даже когда эти функции вызываются в других контекстах или в асинхронном коде.

В примере нижу функция fetchData и все переменные, которые она использует, организованы внутри объекта fetchDataObject. Все внутренние переменные функции, такие как loadingMessage и container, теперь доступны через ключевое слово this. Таким образом, объект fetchDataObject является замыканием, которое сохраняет доступ к своему лексическому окружению, включая переменные и методы.

const fetchDataObject = {
loadingMessage: document.getElementById('loading-message'),
container: document.getElementById('pets-container'),
fetchData: async function() {
const _self = this; // Сохраняем ссылку на текущий объект в переменной _self
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
_self.loadingMessage.style.display = 'none'; // Используем _self для доступа к свойству loadingMessage
_self.container.innerHTML = ''; // Используем _self для доступа к свойству container
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.classList.add('pet');
pet.images.forEach(imageUrl => {
const img = document.createElement('img');
img.src = imageUrl;
petElement.appendChild(img);
});
const info = document.createElement('div');
info.innerHTML = `
<p><strong>Имя:</strong> ${pet.name}</p>
<p><strong>Вид:</strong> ${pet.animal}</p>
<p><strong>Город:</strong> ${pet.city}</p>
<p><strong>Штат:</strong> ${pet.state}</p>
<p><strong>Описание:</strong> ${pet.description}</p>
<p><strong>Порода:</strong> ${pet.breed}</p>
`;
petElement.appendChild(info);
_self.container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
_self.loadingMessage.textContent = 'Ошибка загрузки данных'; // Используем _self для доступа к свойству loadingMessage
}
}
};
fetchDataObject.fetchData();
// Вызываем метод fetchData объекта fetchDataObject для загрузки данных
fetchDataObject.fetchData();

В примере с объектом fetchDataObject, мы используем this внутри объекта для доступа к его свойствам (this.loadingMessage, this.container) и методам. Здесь this указывает на сам объект fetchDataObject, поэтому мы можем обращаться к его свойствам и методам через this.

В то же время, в асинхронной функции fetchData, когда мы создаем переменные loadingMessage и container, мы получаем доступ к этим переменным, используя this, потому что в момент выполнения асинхронной функции контекст может измениться и this может потерять своё значение. Таким образом, мы сохраняем доступ к нужным нам переменным, используя их как свойства объекта this.

А теперь перепишем объект fetchDataObject, используя стрелочные функции.

const fetchDataObject = {
loadingMessage: document.getElementById('loading-message'),
container: document.getElementById('pets-container'),
fetchData: async () => {
const { loadingMessage, container } = fetchDataObject;
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
loadingMessage.style.display = 'none';
container.innerHTML = '';
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.classList.add('pet');
pet.images.forEach(imageUrl => {
const img = document.createElement('img');
img.src = imageUrl;
petElement.appendChild(img);
});
const info = document.createElement('div');
info.innerHTML = `
<p><strong>Имя:</strong> ${pet.name}</p>
<p><strong>Вид:</strong> ${pet.animal}</p>
<p><strong>Город:</strong> ${pet.city}</p>
<p><strong>Штат:</strong> ${pet.state}</p>
<p><strong>Описание:</strong> ${pet.description}</p>
<p><strong>Порода:</strong> ${pet.breed}</p>
`;
petElement.appendChild(info);
container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
loadingMessage.textContent = 'Ошибка загрузки данных';
}
}
};
fetchDataObject.fetchData();

Теперь функция fetchData не создает собственный контекст this, а вместо этого использует контекст объекта fetchDataObject. Мы также использовали деструктуризацию объекта внутри функции для получения доступа к свойствам loadingMessage и container.

Zellij — обновленный tmux на расте

Настройка WordPress + GraphQL + Next.js

Когда-то давно, примерно лет 12 назад, я много работал с WordPress. Хотя он всегда позиционировал себя CMS для блогов, многие делали на нем не только блоги, потому что WordPress предоставлял удобную админку. Темы и плагины писать тоже было удобно. Но с развитием фронтенда со всеми сборщиками, реактами и полной сепарацией от бэкенда, возникла проблема: темы WP всегда были неотделимы от него, а фронтенд становился сложнее. В результате верстать и писать логику на js стало неудобно.

Спустя много лет, я решил посмотреть как же развивается WordPress и с удивлением обнаружил, что он теперь у него есть api. Более того, теперь можно запрашивать данные не только через REST API, но из GraphQL.

Изменилось все, даже способ установки. Так, к примеру, для установки WordPress на локальной машине можно использовать Docker образ для WordPress. Появились решения вроде LocalWP, Kinsta позволяющие запускать WordPress на локальной машине в один клик.

И это очень здорово, потому что команда WordPress решила ту самую проблему разработки тем и адаптировала свою систему под современные задачи.

По url https://yourdomain/wp-json/ доступен список всех роутов.

20241014_084614_8395c6.webp

С помощью плагина WPGraphQL можно добавить поддержку GraphQL.

20241014_084634_6ccae6.webp

Ниже опишу пример создания блога на WordPress + Nextjs.

Установим wordpress. Я использовал LocalWP.

20241014_084653_019a22.webp

Выбираем Create a new site и прокликиваем опции, оставляя все настройки по умолчанию. В результате, работающий на локальной машине WordPress должен выглядеть вот так. Нам нужен адрес сайта: notes.local.

20241014_084711_89d968.webp

Если хотите использовать сразу production сервер, могу посоветовать railway.app. Для простого пет-проекта подойдет бесплатный тариф.

После развертывания проекта, нам потребуется url, по которому будет доступен WordPress. В моем случае это dailynotes.up.railway.app.

20241014_084734_dc26f7.webp

Этот адрес сайта потребуется нам для запросов. Его следует вынести в .env файл, а сам файл добавить в .gitignore, чтобы этот файл не попал на github. Одна из причин по которой так следует делать: чтобы разные настройки не перемешивались. Например, на локальной машине у вас может быть один адрес сайта, а на продакшене другой. Другая причина — в .env файлах можно хранить ключи доступа, которые не должны быть доступны публично.

Следующим шагом устанавливаем nextjs: npx create-next-app@latest.

В корневой директории проекта я сделал файл .env.local и добавил туда url сайта:

NEXT_PUBLIC_WORDPRESS_API_URL=https://dailynotes.up.railway.app

В этом примере я использую сразу путь к сайту на railway. Потому что кроме установки плагина я не планирую никаких изменений в WordPress и хочу сразу получать настоящие данные со своего блога.

Для получения данных мы создадим в корневой файл ./helpers/api.js:

const API_URL = `${process.env.NEXT_PUBLIC_WORDPRESS_API_URL}/graphql
async function fetchAPI(query = '', { variables } = {}) {
const headers = { 'Content-Type': 'application/json' }
const res = await fetch(API_URL, {
headers,
method: 'POST',
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
return json.data
}
export async function fetchNotes() {
const data = await fetchAPI(`
query FetchNotes {
posts(where: {status: PUBLISH}) {
nodes {
id
title
content
date
}
}
}
`)
return data?.posts
}

fetchAPI предназначена для выполнения асинхронных запросов к API с использованием метода POST. Она принимает два аргумента: query (по умолчанию пустая строка) и объект параметров с именем variables (по умолчанию пустой объект).

Для получения постов мы будем использовать fetchNotes(), которая в свою очередь будет использовать функцию fetchApi(). На странице ./page.js можно будет вызвать fetchNotes(), чтобы запросить данные:

import { fetchNotes } from '@/app/helpers/api'
export default async function Home() {
const notes = await fetchNotes()
return (
<section>
{notes.nodes.map((note) => {
return (
<article className="note" key={note.id}>
<h2>{note.title}</h2>
<div className="note-content" dangerouslySetInnerHTML={{ __html: note.content }}></div>
</article>
)
})}
</section>
)
}

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

Настройка MacOS

Этот список переходит из блога в блог и играет роль хранилища ссылок. Программы устаревают, обновляются и все повторяется по кругу.

Самая полезная ссылка — Mac OS X Setup Guide.

В первую очередь

— Atom => VS Code, neovim
iTerm, Warp
— Wunderlist => Singularity

Мессенджеры

— Skype
— Slack
— Telegram

Браузеры

Firefox

Графические программы

— Adobe Photoshop => Pixelmator

Утилиты

Macs Fan Control
The Unarchiver
Transmission
Spotify
VLC Player
Xcode: xcode-select --install

Настройка системы

Mac OS X Setup Guide

Homebrew
ohmyz.sh + .zshrc config
— Git: brew install git
NodeJS