IT-aутстафферы из Тулы💚
https://evapps.ru/
Здесь пишем про веб- и мобильную разработку
▶️ Наш чат для системных аналитиков: https://t.me/pro_sa_evapps
▶️ Посмотреть, как мы живём: https://vk.com/evapps
Post #1522
80
EV @evapps_team
Showing posts older than #1523 · Back to latest
Cache-Control: max-age=3600
public — можно кэшировать и по пути, на CDN и прокси; — private — только в браузере юзера, для персональных данных; — no-store — не кэшировать нигде и никогда (пароли, оплата).max-age на HTML. ETag: "v23-abc123"
304 Not Modified с пустым телом. app.a1b2c3.js) — кэшируем жёстко и надолго:Cache-Control: public, max-age=31536000, immutable
immutable — «даже не перепроверяй». Cache-Control: no-cache
no-cache — это НЕ «не кэшировать». no-cache и no-store — это вообще про разное, и половина болей с кэшем именно из-за того, что их считают одним и тем же. git worktree add ../hotfix -b hotfix main
../hotfix, а твоя недоделанная фича лежит нетронутой в основной папке. git worktree remove.git bisect start HEAD <старый_рабочий_хэш>
git bisect run ./test.sh
test.sh, exit 0 — коммит хороший, не 0 — плохой. Через минуту он выдаёт точный коммит, который всё сломал. curl.git log -S "old_feature_flag" --oneline
-G — то же самое, но по регулярке.git config --global rerere.enabled true
git commit --fixup=<хэш_нужного_коммита>
git rebase -i --autosquash <база>
git log -L :имя_функции:file.py
orders = Order.all # 1 запрос — забрали заказы
for o in orders:
print(o.user.name) # +50 запросов, по одному на заказ
# Django
Order.objects.select_related("user")
# SQLAlchemy
session.query(Order).options(joinedload(Order.user))
# Rails
Order.includes(:user)
WHERE user_id IN (...). WHERE YEAR(created_at) = 2024
WHERE created_at >= '2024-01-01'
AND created_at < '2025-01-01'
% 🔍 Тут всё просто:WHERE name LIKE '%anton' -- бесполезно
WHERE name LIKE 'anton%' -- работает
(a, b) устроен как телефонная книга: сначала сортировка по a, и только внутри неё по b. Поэтому запрос, где есть только b, его не подхватит:INDEX (user_id, created_at)
WHERE user_id = 5 -- норм
WHERE user_id = 5 AND created_at… -- норм
WHERE created_at > … -- мимо, левого префикса нет
-- phone у нас VARCHAR
WHERE phone = 89991234567 -- число против строки, индекс мимо
WHERE phone = '89991234567' -- порядок
is_active, gender и прочие флаги индексировать почти бессмысленно. SELECT * мешает covering index 📦 Иногда индекс уже содержит все поля, которые тебе нужны, и база может отдать ответ прямо из него, не заглядывая в таблицу. SELECT * — и она вынуждена лезть в таблицу за каждой строкой ради остальных колонок. Бери только то, что реально используешь.EXPLAIN ANALYZE и посмотри, что там планировщик на самом деле делает. 
await openai.chat(...), ждёшь ответ и думаешь, что тормозит твой код. Чаще всего код тут ни при чём. Между твоим fetch и текстом ответа лежит физика, железо и география — и большую часть задержки ты не оптимизируешь ни одной строчкой.
<dialog> для модалок. Но половина случаев — это не модалка, а поповер: меню, тултип, дропдаун. И вот тут раньше начинался цирк с z-index, кликом «мимо» и Escape. Теперь два атрибута:<button popovertarget="menu">Меню</button>
<div id="menu" popover>
<a href="#">Профиль</a>
<a href="#">Выйти</a>
</div>
/* карточка, внутри которой есть картинка */
.card:has(img) { padding-top: 0; }
/* форма с невалидным полем */
form:has(:invalid) button { opacity: .5; }
querySelectorAll + навешивание классов вручную. Теперь — одна строка CSS.const id = Date.now() + Math.random();
const id = crypto.randomUUID();
// "6f4d2c1e-8a3b-4f9c-b2e1-7d0a5c3e1f92"
const users = [
{ name: "Аня", role: "dev" },
{ name: "Петя", role: "qa" },
{ name: "Ира", role: "dev" },
];
const byRole = Object.groupBy(users, u => u.role);
// { dev: [Аня, Ира], qa: [Петя] }
Map.groupBy(), если ключом должен быть объект. lodash можно не звать.const rtf = new Intl.RelativeTimeFormat("ru", { numeric: "auto" });
rtf.format(-2, "hour"); // "2 часа назад"
rtf.format(1, "day"); // "завтра"sort() и reverse() десятилетиями мутировали исходный массив — и ловили баги в React, где стейт менять нельзя. Теперь есть неразрушающие версии:const sorted = list.toSorted((a, b) => a - b);
const reversed = list.toReversed();
const patched = list.with(2, "new"); // заменить по индексу
[...arr].sort().document.startViewTransition(() => {
updateTheDOM();
});<main inert>...весь фон...</main>
<dialog open>...</dialog>
tabindex="-1". Теперь — inert. вручную. Теперь браузер сам балансирует строки:h1 { text-wrap: balance; }
p { text-wrap: pretty; }balance — ровные строки для заголовков, pretty — убирает висячие слова в абзацах. Красиво и без ручной вёрстки.#web #frontend #javascript #css #webdev #dev
<dialog id="alert">
<p>Вы уверены? Это нельзя отменить.</p>
<button onclick="alert.close()">Подумаю ещё</button>
<button onclick="doTheThing()">Да, я понимаю</button>
</dialog>
<button onclick="alert.showModal()">Удалить всё</button>
.widget { container-type: inline-size; }
@container (min-width: 500px) {
.widget__body {
display: flex;
gap: 1rem;
}
}.hero {
background: #1a1a2e;
}
@supports (background: oklch(50% 0.2 270)) {
.hero {
background: oklch(20% 0.15 270);
}
}const id = Math.random().toString(36).slice(2);
// где-то в продакшене плачет дев
const arr = new Uint8Array(10);
crypto.getRandomValues(arr);
const id = [...arr].map(n => n.toString(16).padStart(2, "0")).join("");
const sendStats = () => {
navigator.sendBeacon("/api/stats", JSON.stringify(stats));
};
"requestIdleCallback" in window
? requestIdleCallback(sendStats)
: setTimeout(sendStats, 300);.field {
border: 2px solid transparent;
transition: border-color 0.2s;
}
.field:focus-within {
border-color: #6c63ff;
}const queue = [];
window.addEventListener("offline", () => {
queue.push(getPendingData());
});
window.addEventListener("online", async () => {
while (queue.length) {
await sendToServer(queue.shift());
}
});
online говорит только о наличии сети, но не о том, что ваш сервер жив.const Engine = window.SpeechRecognition || window.webkitSpeechRecognition;
if (Engine) {
const voice = new Engine();
voice.lang = "ru-RU";
voice.onresult = ({ results }) => {
handleInput(results[0][0].transcript);
};
voice.start();
}
setInterval для анимаций — это как делать setTimeout(fn, 16) и надеяться на лучшее. Браузер знает точный момент перерисовки, воспользуйтесь этим:let position = 0;
function loop(timestamp) {
position = Math.sin(timestamp / 800) * 120;
el.style.transform = `translateX(${position}px)`;
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
npm install стоит спросить себя: а не встроено ли это уже? 😄#web #frontend #javascript #css #webdevEvApps Готовь сани летом, а архитектуру - на старте проекта! 🚀 Если не хотите через пару лет плакать над контроллерами на 2000+ строк и молиться на God Class - приходите на наш вебинар. ⏰ 2 апреля в 18:30 МСК встречаемся с ведущим fullstack-разработчиком EvApps…
