facebook

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars3,611
mentions72
063126189252Oct 4Oct 18Nov 2
UTC days · repositories facebook owns84 repositories

Package downloads

Cumulative recorded downloads · since 2025-10-18

crates.io—
npm19.3B
PyPI—
04.8B9.6B14.4B19.3BOct 4Oct 18Nov 2
Nov 2crates.io: 0/43 packages observed · npm: 2/125 packages observed · PyPI: 0/12 packages observed

Across 180 known packages in repositories facebook owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete. PyPI excludes known mirrors.

Top repositories

facebook’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside facebook’s account where facebook opened or reviewed pull requests in the past 365 days.

Who noticed facebook

Everyone who starred or forked facebook’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

73posts
Nov 32 posts
  1. Google Translate was crashing our React app — here's how we fixed "removeChild" for good --- title: Google Translate was crashing our React app — here's how we fixed "removeChild" for good published: true description: Our most engaged user hit "Failed to execute 'removeChild' on 'Node'" because Chrome was auto-translating the page. Why it happens, how to reproduce it without Google Translate, and the three-layer fix we shipped. tags: react, javascript, webdev, debugging cover_image: dev-to-uploads.s3.us-east-2.amazonaws.com --- I build QRflows, a dynamic QR code platform. Last week I was going through session recordings and found our most engaged user of the month: someone in Hong Kong who spent 11 minutes in the dashboard, clicked 82 times, tried half a dozen QR types, added a logo, built a landing page. At minute three, the page crashed and reloaded. The error in the console: NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node. The recording made the cause obvious once I noticed it: the whole dashboard was in Chinese. Our site only exists in English. The user had Chrome's built-in Google Translate turned on. This is one of those bugs that never shows up in your own testing, because you never translate your own app. Here's what's going on, how to reproduce it without Google Translate, and the three-layer fix we shipped. --- Why Google Translate breaks React React keeps references to the DOM nodes it created, including plain text nodes. When state changes, it updates or removes those exact nodes. Google Translate doesn't know about React. When it translates a page, it walks the DOM and replaces text nodes with its own <font> elements containing the translated text: <!-- What React rendered --> <button>Save</button> <!-- What the DOM looks like after Google Translate --> <button><font><font>保存</font></font></button> The original "Save" text node is gone from the DOM, but React still holds a reference to it. The next time React needs to remove or replace that text, it calls parent.removeChild(textNode) — and the browser throws, because that node is no longer a child of anything. The same thing happens with insertBefore when React tries to insert something next to a node that Translate has swapped out. It's a long-standing issue — facebook/react#11538 has been open since 2017 — and it isn't specific to React: anything that holds references to text nodes is vulnerable. If you want the deep dive, Martijn Hols' write-up is excellent. Which code is vulnerable The crash needs two things: a bare text node and something conditional next to it. Patterns like these: // Text sitting next to a conditional element <button> {isSaving && <Spinner />} Save </button> // Conditional text {error && error.message} // Ternary returning strings <span>{isSaving ? "Saving…" : "Saved"}</span> // Text mixed with an expression <p>Uploaded {count} files</p> Every QR builder has dozens of these: status labels, counters, "Uploading… / Done", plan badges. Which is why the crash hit our most active user and nobody else. --- Reproducing it without Google Translate Waiting for a translated session to show up in your logs is not a test plan. You can simulate what Translate does to the DOM in the browser console: function fakeTranslate(root = document.body) { const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode: (node) => node.nodeValue.trim() && node.parentElement && !node.parentElement.closest('script, style, [translate="no"], font') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT, }); const nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); for (const textNode of nodes) { const outer = document.createElement('font'); const inner = document.createElement('font'); inner.textContent = '译' + textNode.nodeValue; // mark it as "translated" outer.appendChild(inner); textNode.parentNode.replaceChild(outer, textNode); } return nodes.length; } window.addEventListener('error', (e) => console.log('CRASH:', e.message)); fakeTranslate(); Paste it, run fakeTranslate(), then use the app: switch tabs, toggle options, type into inputs, navigate between routes. Run fakeTranslate() again after each step, because real Translate keeps re-translating new content as it appears. On our dashboard, switching QR types after this reproduced the exact crash from the recording within a few clicks. --- The fix: three layers There's no single clean fix. What worked for us is three layers, from "never crash" to "fewer weird states". Layer 1: a DOM guard so the crash can't happen This is the workaround from the React issue thread: patch removeChild and insertBefore so that, instead of throwing, they skip the operation when the node relationship is no longer what React expects. // src/lib/domTranslateGuard.ts export function installDomTranslateGuard(): void { if (typeof window === 'undefined' || typeof Node !== 'function') return; const proto = Node.prototype as any; if (proto.__translateGuardInstalled) return; proto.__translateGuardInstalled = true; const originalRemoveChild = proto.removeChild; proto.removeChild = function (child: Node) { if (child.parentNode !== this) { console.warn('[dom-guard] removeChild skipped', child, this); (window as any).clarity?.('event', 'dom_guard_removeChild'); return child; } return originalRemoveChild.call(this, child); }; const originalInsertBefore = proto.insertBefore; proto.insertBefore = function (newNode: Node, referenceNode: Node | null) { if (referenceNode && referenceNode.parentNode !== this) { console.warn('[dom-guard] insertBefore skipped', referenceNode, this); (window as any).clarity?.('event', 'dom_guard_insertBefore'); return newNode; } return originalInsertBefore.call(this, newNode, referenceNode); }; } Two details that matter: Install it before React touches the DOM. We call it as the very first thing in the client entry, before hydrateRoot: // entry-client.tsx import { installDomTranslateGuard } from './lib/domTranslateGuard'; installDomTranslateGuard(); // ...then hydrateRoot(...) Keep it out of SSR. It only lives in the client entry, and the typeof window check is a second line of defence. The server never touches Node.prototype. The trade-off is honest: this hides the error instead of fixing the cause. A skipped insertBefore can occasionally leave a piece of UI missing until the next render. That's why we log it and send a custom event to our session analytics — so we can see how often the guard actually fires, and on which pages. Layer 2: stop giving Translate bare text nodes The guard prevents crashes. Wrapping text in elements prevents most of the situations where it would have to step in. If text lives inside its own <span>, Translate replaces the text inside the span, and React only ever adds or removes the span itself — which is still there. // Before <button> {isSaving && <Spinner />} Save </button> // After <button> {isSaving && <Spinner />} <span>Save</span> </button> // Before {error && error.message} // After {error && <span>{error.message}</span>} // Before <p>Uploaded {count} files</p> // After <p><span>Uploaded {count} files</span></p> No visual change, no new classes. We went through every component in the QR builder, the file upload flow, the editor and the download modals, and wrapped anything that changes at runtime: status labels, counters, button text next to spinners. Layer 3: translate="no" — but only where it makes sense The tempting shortcut is <html translate="no">. Don't. Your users turned on translation because they need it — breaking the crash by breaking the translation just moves the problem. We only opt out content that shouldn't be translated anyway: - URL, email, phone and password inputs - short links and destination URLs - the QR preview and technical labels (sizes, PNG/SVG) - the brand name in the header and footer <input type="url" translate="no" /> <span translate="no">{shortUrl}</span> Everything else — headings, buttons, help text — still gets translated. --- How we verified it After deploying, I ran the fake-translate script on the live site: the public PDF QR generator, then the logged-in dashboard. I switched QR types back and forth, toggled the logo option, typed into inputs, and navigated from the builder to the landing page editor and back — re-running fakeTranslate() after each step. Result: zero uncaught errors, no reloads, and the UI kept updating live with the "translated" text in place. The brand name and URL fields stayed untouched, as intended. We'll keep watching the dom_guard_* events in session analytics. If they show up a lot on a specific page, that page still has bare text nodes to wrap. --- TL;DR - Google Translate replaces text nodes with <font> elements. React still holds the old nodes and crashes on removeChild / insertBefore. - You can reproduce it without Translate by wrapping text nodes in <font> from the console. - Layer 1: patch removeChild / insertBefore before hydration, client-only, and log when it fires. - Layer 2: wrap conditional and dynamic text in <span>. - Layer 3: translate="no" only on inputs, URLs and brand names — never on the whole app. The part that stuck with me: this bug hit the single most engaged user we had that month. International users who translate your app are often exactly the people working hardest to use it. --- I'm building QRflows in public — dynamic QR codes with Smart Rules, real-time analytics and a Claude MCP integration. If you've hit this bug with a different framework, I'd like to hear how you handled it.

    1 reaction · 2 comments
  2. Ускоряем self-hosted Convex в два раза: AOT-компиляция TypeScript-функций в Wasm Обзор Convex – это новый, но набирающий популярность фреймворк для реактивных веб-приложений, который позволяет писать транзакционные квери (queries) и мутации (mutations) на TypeScript с гарантиями консистентности. Вся статья ниже относится к self-hosted версии Convex: convex-backend, а точнее, к моей пропатченной версии оного, так как “официальная” версия не пригодна к работе в продакшене и вряд ли тестировалась на сколь-нибудь нагруженном кейсе. Моя версия работает на виртуальном сервере с 32 ядрами и 64Гб памяти и обрабатывает до 15 тысяч Convex-кверей и мутаций в минуту. Я навайбкодил convex-wasm-compiler, который компилирует Convex-квери и мутации в WebAssembly, и соответствующий патч для convex-backend, позволяющий выполнять скомпилированные в Wasm Convex-функции на файберах Wasmtime прямо в Tokio рантайме convex-backend. TLDR: квери и мутации в среднем работают в 2 раза быстрее, хвосты задержек сократились втрое. Объём памяти, который сервер удерживает под готовые к выполнению окружения (V8-контексты или Wasm-окружения), уменьшился на 80%. До этого для быстрого выполнения Convex-функций приходилось активно кешировать и переиспользовать V8-контексты. Без переиспользования каждая Convex-функция стартует в новом контексте: модули ещё не загружены, JIT не прогрет. Начинается загрузка модулей приложения, необходимых для выполнения функции, и интерпретация её JS-кода. Даже относительно простые Convex-функции в среднем выполняются за 100–150 мс. По мере роста приложения и размера модулей проблема усугубляется. Convex-компоненты помогают сдерживать бесконтрольный рост модулей, но сами добавляют столько накладных расходов и обвязки, что для многих приложений, на мой взгляд, от них больше вреда, чем пользы. Особенно для приложений, которые разрабатывает один человек или пара разработчиков: там нет вопросов разделения ответственности за подсистемы, стабильности API и так далее. Своё отношение к разработке на Convex в стиле «majestic monolith» и к подходу с большим количеством компонентов я описал в посте о своём приложении. Если выполнять Convex-функцию в уже прогретом V8-контексте, где все модули загружены, а код функции скомпилирован в машинный код через TurboFan, время выполнения часто сокращается очень заметно — вплоть до 10 раз. Например, со 100–150 мс до 10–15 мс, при условии, что строки базы данных, которые читает функция, уже закешированы в памяти. Так что переиспользование V8-контекстов было и остаётся необходимым для быстрых и эффективных Convex-приложений. Но у него есть две проблемы. Потребление памяти закешированными V8-контекстами Во-первых, чтобы больше 99% вызовов использовали уже готовые V8-контексты, каждому V8-изоляту в рантайме convex-backend нужно держать не один контекст. Нужен отдельный контекст для каждого модуля — точнее, группы инициализации модулей, — который convex-backend часто назначает на выполнение этому конкретному изоляту. В конфигурации self-hosted convex-backend для моего приложения сейчас используются TinyLFU-кеши размером до 6+1 контекстов (на момент публикации поста о моей конфигурации self-hosted Convex было 5+1). Всего кешируется до 400 контекстов, хотя даже на пике нагрузки одновременно выполняется гораздо меньше 400 Convex-функций. Главная причина такой неэффективности — V8-контексты привязаны к V8-изолятам, и использовать один контекст в разных изолятах нельзя. Каждый V8-контекст занимает около 35 МБ памяти, и эта цифра тоже растёт вместе с приложением: два месяца назад я писал, что в моём приложении один V8-контекст удерживал в среднем 30 МБ. В сумме эти контексты занимают около 14 ГБ памяти на моём сервере convex-backend. Волны инициализации V8-контекстов после деплоя Convex-приложения Вторая проблема переиспользования V8-контекстов в том, что при деплое Convex-приложения часть закешированных контекстов инвалидируется. Это может запустить по всему серверу волну загрузки и компиляции модулей, которая длится около минуты: сотни V8-контекстов приходится создавать и прогревать заново. V8-изоляты не делятся друг с другом оптимизированным машинным кодом. Даже если сериализованный байткод закеширован, инициализация модулей всё равно выполняется отдельно в каждом V8-контексте. Масштаб инвалидации зависит от того, какие модули изменились при деплое. Но если обновились именно ключевые модули с «горячими» Convex-функциями, на которые приходится значительная доля всей серверной нагрузки, большая часть V8-контекстов может пойти под снос и потребовать повторной инициализации. Ведь с каждым таким модулем связано множество V8-контекстов в TinyLFU-кешах разных V8-изолятов. Эта волна повторного прогрева V8-контекстов совпадает с другой волной работы, которую вызывает деплой Convex-приложения: кеш результатов кверей из обновлённых модулей инвалидируется, а квери, на которые у клиентов открыты реактивные подписки, выполняются заново. Это происходит, даже если данные в read set не менялись. То есть серверу приходится не только компилировать код и загружать модули, но ещё и выполнять больше кверей, чем в обычном режиме. На практике из-за этих волн работы после деплоя квери и мутации могут какое-то время выполняться медленнее, а при сильной перегрузке серверу приходится отклонять часть вызовов. Рабочие потоки Tokio рантайма Convex конкурируют с рабочими потоками V8-изолятов и фоновыми потоками V8 Это более общая проблема выполнения Convex-функций в V8-изолятах, не специфичная для переиспользования контекстов. convex-backend ограничивает число воркеров V8-изолятов, которые могут активно выполнять код, через переменную FUNRUN_ISOLATE_ACTIVE_THREADS. Под активным выполнением здесь понимается работа без блокировки на чтении или записи в базу данных и другом I/O. Эту переменную следует выставлять равной CPU-лимиту контейнера convex-backend или чуть ниже. При этом изолировать эти потоки от собственного Tokio рантайма convex-backend и фоновых потоков V8, как правило, не получится без существенного снижения максимальной пропускной способности сервера при длительной нагрузке. Число потоков ОС у Tokio рантайма задаётся переменной RUNTIME_WORKER_THREADS. Фоновые потоки движка V8 отвечают за компиляцию и сборку мусора в V8-контекстах; их число задаётся переменной V8_THREADS. В моей конфигурации сервера CPU-лимит convex-backend составляет 28.5 CPU, FUNRUN_ISOLATE_ACTIVE_THREADS=28, RUNTIME_WORKER_THREADS=28 и V8_THREADS=16. Кроме того, у меня есть несколько пулов выполнения Node, в каждом из которых по 1–2 активных потока. Получается, что теоретически в процессах контейнера с лимитом 28.5 CPU может быть как минимум 75 активных потоков ОС. Такой oversubscription — не катастрофа, но до оптимума далеко. За последнюю неделю, когда Convex-квери и мутации на моём сервере выполнялись в V8, в пятиминутном интервале с наибольшим контеншеном CPU pressure достигал 29%. Разумеется, это пришлось на время деплоя приложения. Практический эффект этого CPU pressure: когда сервер занят — из-за деплоя или просто роста нагрузки, — Convex-функции выполняются медленнее, чем при умеренной нагрузке. И это отдельный эффект, помимо замедления чтения и записи в базу данных, более частого вытеснения записей из кеша результатов кверей, кеша индексов и кеша V8-контекстов. Сама база под Convex, например MySQL или PostgreSQL, при росте транзакционной нагрузки тоже может испытывать повышенный CPU pressure, а её контейнер — попадать под троттлинг. Зачем компилировать TypeScript-функции Convex в Wasm Перенести выполнение горячих функций из V8 в WebAssembly-рантайм вроде Wasmtime, встроенный в Tokio рантайм Convex, — очевидный ответ на описанные проблемы. В начале 2026 года команда Convex объявила, что Wasm-рантайм есть в планах. В августе Wasm-рантайм добавили в репозиторий convex/convex-backend. Однако на следующий день его удалили, и с тех пор он не возвращался. В той реализации использовался интерпретатор QuickJS, скомпилированный в Wasm: сами Convex-функции интерпретировались внутри Wasm, а не компилировались в него. Поэтому реализация была очень медленной и не предназначалась для продакшена. Обычно предполагается — и руководство Convex озвучивало это на разных Q&A-сессиях начиная с августа, — что ради производительности WebAssembly-рантайма разработчики будут писать Convex-функции на компилируемом языке с Wasm-таргетом, прежде всего на Rust. Я начал разрабатывать компилятор обычных Convex-функций, написанных на TypeScript, в байткод WebAssembly с помощью Static Hermes — AOT-компилятора JavaScript с поддержкой Wasm-таргета — в конце июля. Тогда ещё не было известно, что команда Convex собирается ускорить работу над Wasm-рантаймом: до августа Jamie Turner говорил лишь, что тот, скорее всего, появится только в 2027 году. За весь проект, который начался в июле и занял почти три месяца, встраивание Wasmtime в convex-backend оказалось сравнительно небольшой частью работы — возможно, около 10% всех усилий. То есть само по себе добавление Wasm-рантайма в convex-backend — задача относительно простая, и это было мне более-менее понятно с самого начала. Правда, некоторые другие части задачи я сильно недооценил: возможно, я рассчитывал, что на Wasm-рантайм в convex-backend уйдёт 50% усилий. В итоге оказалось только 10%, а весь проект занял в пять раз больше времени, чем я ожидал. Так что вариант переписать части моего Convex-приложения на Rust я рассматривал уже тогда. Но не пошёл по этому пути по нескольким причинам: В приложении было около 350 тысяч строк продакшен-кода только в Convex, причём значительная часть логики была общей с другими частями системы, работавшими на Node.js. Вместе с ними объём продакшен-кода превышал полмиллиона строк. Переписать всё это казалось примерно такой же сложной задачей, как сделать компилятор существующих Convex-функций в WebAssembly. Оглядываясь назад, даже переписывание полумиллиона строк с TypeScript на Rust, вероятно, заняло бы меньше времени, чем трёхмесячная разработка компилятора. Но тогда я этого не предвидел. С другой стороны, у такого проекта наверняка были бы свои существенные риски, о которых я сейчас не знаю, потому что не пробовал. И он точно нарушил бы привычный темп выпуска фич. Меня беспокоило время компиляции Rust и то, насколько оно может замедлить цикл разработки. Эти опасения отчасти подтвердились: с тех пор я полностью написал одну часть приложения на Rust, используя добавленную мной в convex-backend функциональность native resident. Это не Convex action, кверя или мутация, а фоновый Rust-процесс, который деплоится вместе с Convex-приложением и сам вызывает Convex-квери и мутации через loopback. В этой части приложения всего 30 тысяч строк Rust, но каждый раз, когда её код меняется, я ощущаю всю боль медленной компиляции. Мне не хотелось придумывать API для работы с Convex на Rust — аналоги ctx.db.query(), insert(), defineSchema() и так далее, — которые потом вытеснят официальные API команды Convex. В целом я хотел сделать что-то, что могло бы дополнить будущие разработки Convex, а не разойтись с ними или конкурировать. Наконец, делать компилятор мне просто казалось интереснее, чем переписывать приложение на Rust! Устройство convex-wasm-compiler и Wasm-рантайма convex-backend Код Convex-приложения не меняется: это по-прежнему TypeScript с SDK convex-js. Компиляция TypeScript-кода Convex-приложения в Wasm convex-wasm-compiler управляет анализом исходников, сборкой JavaScript-бандлов и нативной компиляцией. С помощью tsrs он находит регистрации Convex-функций и связывает экспортируемые маршруты с исходным кодом, а с помощью esbuild разрешает зависимости и преобразует TypeScript-код приложения в JavaScript-бандлы. Написанный на Rust анализатор использует Oxc, чтобы проверить исходники и граф зависимостей на небезопасное сохранение состояния между запросами и определить, можно ли переиспользовать инициализированные модули. Результаты анализа определяют, что можно допустить к выполнению и как это упаковать; исполняемый код приложения берётся из JavaScript, который генерирует esbuild. Затем компилятор подготавливает этот JavaScript в виде модулей приложения, добавляет bridge к host-операциям Convex и вызывает (Static) Hermes. Здесь и далее «host» — это операции рантайма convex-backend, такие как чтение и запись в базу данных, а «guest» — выполнение функций приложения. Компилятор shermes генерирует C-код, который тулчейн Clang/LLVM из Emscripten компилирует вместе с Hermes рантаймом и нативным адаптером Convex. wasm-ld линкует эти компоненты, а Binaryen оптимизирует полученный Core WebAssembly. Прилинкованный Hermes рантайм обеспечивает внутри Wasm-guest JavaScript-объекты, строки, промисы, исключения и сборку мусора. Асинхронные JS-функции на файберах Wasmtime Static Hermes компилирует асинхронные JavaScript-функции в конечные автоматы, которые могут приостанавливаться и продолжать выполнение с опорой на промисы. Патч для Hermes переносит основную механику промисов в C++: состояние промиса хранится в объектах JSPromise, которыми управляет GC, а реализация Promise на C++ отвечает за resolve, reject и учёт continuations. Это сокращает количество операций с JavaScript-объектами и аллокаций колбэков при асинхронном выполнении. Некоторые высокоуровневые операции Promise по-прежнему используют внутреннюю JavaScript-реализацию Hermes. C+±адаптер Convex, скомпилированный в WebAssembly вместе с Hermes и приложением, содержит цикл, который доводит каждый вызов до завершения. Он вызывает хендлер, обрабатывает очередь микротасок Hermes, выполняя готовые continuations, ждёт чтения и записи в базу данных и других host-операций, а по мере поступления результатов делает resolve или reject соответствующих промисов. Если нужно дождаться асинхронной работы на стороне host, Wasmtime приостанавливает файбер вызова, освобождая рабочий поток Tokio для других задач. Когда результаты готовы, выполнение продолжается в том же файбере. Таким образом, экспортируемая точка входа Wasm возвращает итоговый статус вызова через обычный интерфейс «вызов — возврат»; JavaScript-промисы и continuations остаются внутри guest. Tokio рантайм Convex поллит call_async из Wasmtime, который выполняет точку входа guest в одном файбере Wasmtime. Проверки fuel и epoch обеспечивают соблюдение лимитов выполнения и дают CPU-bound коду внутри guest возможность делать yield. Компиляция Wasm в нативный код и деплой В этой схеме нативная компиляция происходит на машине разработчика или в CI. Компилятор Cranelift из Wasmtime переводит .wasm-модули — приложение, Hermes рантайм и адаптер Convex — в машинный код под целевую архитектуру сервера и конфигурацию Wasmtime. Wasmtime сохраняет этот код и метаданные для его загрузки в .cwasm-файлах. При деплое они отправляются на сервер вместе с переносимыми .wasm-файлами, обычным JavaScript-бандлом Convex и схемой. На сервере convex-backend проверяет, что скомпилированные файлы совместимы с его движком Wasmtime и соответствуют той версии JavaScript-приложения, которая сейчас деплоится. Сохранённые хеши содержимого не позволяют случайно совместить новый JavaScript со старой скомпилированной сборкой. convex-backend загружает скомпилированный код в память и подготавливает изолированные окружения для повторного использования, каждое со своим Hermes рантаймом и JavaScript-состоянием приложения. Инициализация модулей — выполнение кода верхнего уровня, который создаёт объекты модулей и регистрирует экспортируемые функции, — завершается для начального пула окружений ещё до того, как новая версия приложения начинает обслуживать квери и мутации. Позже пул может расти вслед за нагрузкой. Именно эта часть решает описанную выше проблему «Волны инициализации V8-контекстов после деплоя Convex-приложения», характерную для выполнения в V8. Wasm-окружения и управление памятью Wasm-рантайм экономит память по сравнению с V8 за счёт того, что изолированные окружения в пуле совместно используют скомпилированный код и инициализированную память приложения. Эти окружения похожи на V8-изоляты в рантайме Convex, но у них нет выделенных потоков ОС. Их количество ограничено общим бюджетом памяти и жёстким лимитом числа инстансов; число одновременно активных выполнений ограничивается отдельно, исходя из CPU. Каждое окружение управляется Wasmtime Store. В нём есть Hermes рантайм и собственное JavaScript-состояние, и в каждый момент оно обслуживает один вызов Convex-функции. Окружения с одинаковыми скомпилированными модулями совместно используют их машинный код. На Linux convex-backend также сохраняет инициализированную Wasm-память окружения, включая JavaScript-хип, в неизменяемый снапшот в файле. Это происходит после инициализации модулей приложения, но до выполнения в этом окружении первой квери или мутации. Другие окружения мапят этот снапшот с copy-on-write: неизменённые страницы используют общую физическую память, а при записи создаются приватные копии. Каждое окружение заново создаёт собственные таблицы функций и ссылки рантайма, а состояние конкретного вызова Convex-функции хранит отдельно. Перед переиспользованием окружения convex-backend проверяет, что конфигурация и другие зависимости, прочитанные при инициализации, всё ещё актуальны. В сборщик мусора Hermes, Hades, внесён патч, который сохраняет это совместное использование памяти. В данной Wasm-сборке Hades работает в одном потоке внутри guest-вызовов. Подготовленные области хипа исключены из последующих аллокаций и компактизации: это уменьшает количество записей в общую исходную память. При этом сохраняются обычные механизмы обхода графа объектов (tracing), очистки памяти от недостижимых объектов (sweeping) и финализации. В безопасных точках между вызовами сборка мусора и анализ состояния аллокатора находят пустые области nursery и заведомо свободные страницы с данными, чью приватную физическую память host может освободить. Бэкенд учитывает общую физическую память только один раз и резервирует память под приватное состояние и рост каждого окружения. Хуки Wasmtime отслеживают записи и рост памяти, поэтому общий бюджет Wasm-памяти соблюдается как при выполнении вызовов, так и при добавлении окружений в пул. Wasmtime также использует сборщик с отложенным подсчётом ссылок для объектов, которыми владеет сам движок, в том числе перехваченных Wasm-исключений. Это отдельный механизм, не связанный с Hades, который управляет JavaScript-объектами в линейной памяти. Вместе с ограничением числа сохраняемых инстансов и освобождением страниц эти механизмы позволяют переиспользовать инициализированные рантаймы после успешных вызовов, не удерживая бесконечно всю память, которую затрагивали предыдущие выполнения Convex-функций в этом окружении. Wasm vs. V8: результаты замеров производительности В целом среднее время выполнения кверей и мутаций в Wasm сократилось почти на 50% относительно V8: Рантайм Среднее p90 p99 Максимум V8 38.53 мс 62 мс 329 мс 7 463 мс Wasm 19.61 мс 34 мс 139 мс 2 242 мс В среднем каждое Wasm-окружение удерживает 12 МБ полностью приватной памяти, не разделяемой с другими окружениями. Это на 66% меньше, чем 35 МБ у V8-контекста. Кроме того, держать в памяти нужно гораздо меньше Wasm-окружений: 130 против примерно 400 V8-контекстов. Почему V8-контекстов нужно больше, чем одновременно выполняющихся вызовов, я объяснил выше в разделе «Потребление памяти закешированными V8-контекстами». В пятиминутном интервале с наибольшим контеншеном CPU pressure достигал 15%, когда основным рантаймом был Wasm, против 29% для V8. Даже эти 15%, вероятно, отчасти связаны с сохранившимся выполнением в V8. Как я расскажу ниже, я компилирую в Wasm не всё Convex-приложение, а лишь примерно 20% самых горячих модулей, на которые приходится 90% всей нагрузки. Это экономит время компиляции и уменьшает размер артефактов. Остальные 80% модулей по-прежнему выполняются в V8, поэтому для них всё ещё актуальны инвалидация и инициализация контекстов, работа фоновых потоков V8 и так далее. В следующих двух таблицах показано среднее относительное снижение метрик mean/p90/p99/max для отдельных кверей и мутаций в разных группах. Эти средние не взвешены по числу вызовов или суммарному времени выполнения каждой функции. Время выполнения целиком: Группа Среднее p90 p99 Максимум Все квери −57.6% −56.3% −65.3% −82.2% Квери: 20% с наибольшим числом host-операций −39.3% −37.6% −48.4% −82.2% Квери: 20% с наименьшим числом host-операций −69.0% −61.3% −78.0% −84.0% Все мутации −51.5% −33.7% −60.6% −75.1% Мутации: 20% с наибольшим числом host-операций −39.4% −43.6% −48.9% −70.5% Мутации: 20% с наименьшим числом host-операций −51.2% −55.7% −61.5% −70.6% «User» time — оценка времени, которое функции провели непосредственно в guest-рантайме, без ожидания чтения и записи в базу данных и других асинхронных host-операций: Группа Среднее p90 p99 Максимум Все квери −70.6% −66.9% −76.1% −79.8% Квери: 20% с наибольшим числом host-операций −43.5% −41.0% −56.6% −89.0% Квери: 20% с наименьшим числом host-операций −89.5% −82.9% −86.6% −69.8% Все мутации −67.7% −46.5% −72.5% −59.9% Мутации: 20% с наибольшим числом host-операций −41.0% −41.2% −47.7% −39.0% Мутации: 20% с наименьшим числом host-операций −76.9% −78.6% −82.2% −55.0% «20% с наибольшим числом host-операций» и «20% с наименьшим числом host-операций» — это функции, которые в среднем выполняют больше или меньше асинхронных операций, например чтений и записей в базу данных, по сравнению с остальными. Например, функция с единственным ctx.db.get(id) делает одну асинхронную операцию и попадёт в группу «20% с наименьшим числом». А функция, которая в среднем выполняет сотни операций ctx.db, ctx.scheduler и так далее, — в группу «20% с наибольшим числом». Из таблиц видно, что относительное преимущество Wasm перед V8 обычно уменьшается для функций, которые делают очень много асинхронных операций. Возможно, это указывает на слабое место в обработке JS Promise внутри файбера Wasmtime, описанной выше в разделе «Асинхронные JS-функции на файберах Wasmtime». И это несмотря на то, что оптимизации этой части уже уделено много внимания и я не знаю — ладно-ладно, GPT-Astra не знает, — как ускорить её ещё. Другая возможная причина: частые yield из файберов Wasmtime и переключения потока на другие задачи Tokio приводят к тому, что Convex-вызов чаще мигрирует между ядрами CPU, а значит, чаще получает промахи L1-кеша или TLB. А возможно, дело в чём-то ещё. Причина пока не установлена. Преимущество по «user» time тоже не универсально. Есть одна Convex-мутация, у которой среднее user time в Wasm-рантайме на 14% выше, чем в V8, хотя по полному времени выполнения она всё равно быстрее в Wasm. Эта мутация в цикле выполняет численные операции над Float64Array размером более 100 тысяч элементов. Возможно, в V8 для этого есть интринсики или векторизация, которых пока не хватает в пайплайне Hermes → Wasm. Операционные аспекты AOT-компиляции в Wasm Теневое выполнение для постоянного тестирования Я добавил в convex-backend целую сложную подсистему для дифференциального тестирования и теневого выполнения (shadow execution) Convex-функций в рантаймах V8 и Wasm. Она поддерживает основное (primary) и теневое выполнение — в каждой роли может быть как V8, так и Wasm. Заданная доля вызовов, например 5%, случайным образом отбирается для теневого выполнения, после чего система проверяет, что результаты совпадают. Теневые вызовы не блокируют основные. Теневое выполнение работает и для кверей, и для мутаций. В случае мутаций записывается трейс вызовов, меняющих состояние, таких как db.insert(), но сами строки в базе, конечно, не перезаписываются второй раз. Поменять местами основной и теневой рантаймы можно без повторного деплоя приложения. Эта система позволила мне уверенно переключить продакшен на Wasm и найти несколько багов в convex-wasm-compiler. Правда, большинство багов оказалось в самой подсистеме теневого выполнения, сравнения и проверки трейсов. Даже после переключения основного выполнения на Wasm я продолжаю выполнять 5% вызовов в V8 в теневом режиме, чтобы замечать расхождения, если они появятся в будущем. Выборочная компиляция в Wasm И компилятор, и convex-backend поддерживают выборочную компиляцию: в Wasm компилируется и выполняется только часть модулей. Это помогает сократить время компиляции и объём данных, передаваемых при деплое. В своём приложении я выбираю лишь около 20% модулей, на которые приходится более 90% всех вызовов кверей и мутаций. Однако ни объём передачи, ни время компиляции не уменьшаются впятеро: из-за транзитивных зависимостей компилировать и передавать всё равно приходится около 70% всех модулей приложения. Так что, возможно, выигрыш не так уж велик. Время компиляции и объём передачи Выбранные 20% модулей моего Convex-приложения — это 57 модулей и 631 Convex-функция для компиляции в Wasm: 145 кверей и 486 мутаций. Всего компилируется 528 отдельных JS-модулей: 57 выбранных плюс их зависимости. Для этого набора холодная компиляция — когда нужно перекомпилировать большую часть кода или весь код, например после очистки кеша компиляции, — с 16 воркерами AOT-компиляции и лимитом памяти 12 ГБ занимает около 6 минут. Тёплая компиляция, когда часть модулей изменилась, но большинство уже есть в кеше, занимает около 2 минут. До идеала далеко, и я продолжаю над этим работать. Когда я начинал проект, я не ожидал, что именно скорость компиляции, инкрементальность и кеширование окажутся главными инженерными проблемами. Из примерно трёх месяцев работы над проектом AI-агенты суммарно потратили около трёх недель календарного времени исключительно на это направление. При деплое с таким набором скомпилированных модулей на мой self-hosted сервер Convex передаётся около 50 МБ сжатых артефактов. Без Wasm — около 15 МБ, то есть Wasm примерно втрое увеличивает объём данных при деплое. Без выборочной компиляции он, вероятно, был бы примерно в пять раз больше, чем при деплое одного JS-бандла. Эта статья — перевод из моего блога про работу с Convex в продакшене.

  3. YouTube · · YouTube description · AI Security Table · facebook/sapling

    Anthropic Ditched Git. Here’s Why Your Agents Will Too Anthropic got tired of Git. With merges jumping from about 2,500 a day in May to more than 30,000 by September, a seven person team built Scion, a centralized, always online source control system designed for agents instead of people. Chris sees the start of rebuilding everything around agents, while Matt keeps pointing out that write ahead logs, RPC and server side merges are SVN and Perforce with better marketing, borrowed from Google's Piper and Meta's Sapling and Mononoke. Izar asks what happens when infrastructure gets so comfortable for agents that humans stop looking, and Matt notices the article barely mentions security until Chris finds the access control section. Along the way the hosts take on AI job interviews, Claude's excessive validation during an adversarial review, LLM knockoffs of Adobe, Microsoft and beehiiv, and why Norton is still selling fear to people who don't know better. Everything old is new again; it just runs a thousand times faster. 🚀 Join the Conversation Is rebuilding infrastructure around agents real progress, or are we quietly optimizing humans out of the review loop? Tell us in the comments. Redesigning source control at Anthropic: anthropic.com Mononoke: github.com/facebook/sapling/tree/main/eden/monono… Geim becomes first Nobel & Ig Nobel winner: improbable.com Creator of Free Adobe Suite Strikes Again, Reverse Engineers Microsoft Office: futurism.com PhotoCraft: storytold/photocraft Follow AI Security Table: ➜ Home: securitytable.ai ➜ X: x.com ➜ LinkedIn: linkedin.com 00:00 Cold Open: Welcome Aboard the Starship Enterprise 02:07 Mythbusters and Laser Blasters 03:35 The Ig Nobel Awards 04:54 From Levitating Frogs to a Real Nobel Prize 06:07 An AI Negotiates a Lower Salary 06:44 Job Interviews With a Model 07:52 Resume Filters and Keyword Bingo 09:03 Let Humans Do the Hiring 10:38 Article Intro: Anthropic Doesn't Use Git 11:05 Memory Lane: CVS and Visual SourceSafe 11:49 Meet Scion 12:58 A Seven Person Team With Unlimited Tokens 13:53 Why Not Perforce or SVN? 15:12 Built on Google's Piper and Meta's Sapling 16:31 No Offline Mode in an Agentic World 17:42 Matt's Case: An LLM Librarian for Your Repo 19:25 Write Ahead Logs: Everything Old Is New Again 20:36 Centralized, API Driven Source Control 21:54 Not a General Replacement for Git 22:26 Closed Source, and an Ad for Claude 23:11 Is This Actually Exciting? 23:53 Izar's Rebuttal: Optimizing Infrastructure for Agents 24:59 SCN, Not SVN 26:20 Where's the Security? 27:30 It's Basically a Distributed File System 27:57 Monorepos Are Back 30:12 Claude's Excessive Validation 31:38 Reasonable Security and Pepperidge Farm 33:35 Inspired by Meta's Mononoke 35:01 LLM Knockoffs of Adobe and Microsoft 36:30 Cloning beehiiv in 24 Hours 38:56 LifeLock, Norton, and Predatory Security Sales 42:03 Closing Statements 44:37 Outro Thanks for Watching!

    5 likes · 4 comments · 744 views
  4. 新卒エンジニアが書き溜めた技術メモ 4選 はじめに こんにちは、namonaki です。新卒で入社して半年、分からなかったことを個人用のメモに書き留めてきました。その中からいくつか選りすぐって、改めて調べつつ記事にしてみました。 ■ 対象 Rails や React でコードは書けるが、「なぜこう書くと動くのか」はまだ説明できない人 普段なんとなく使っている仕組みの裏側を、ソースコードや DevTools で確かめてみたい人 ■ 扱うこと ブラウザ・Web標準 React と Islands Architecture Rails テスト 各章は独立しているので、気になるところから読んでください。参考にした資料は、本文中の番号から末尾の脚注に飛べます。 ! 前提のバージョン(2026年10月時点) Ruby 4.0 / Rails 8.1 React 17 / Node.js 24 factory_bot 6.5 1. ブラウザ・Web標準 JSON-LD(検索エンジン向けの構造化データ)について調べていたとき、JS を書くわけでもないのに、なぜ <script> タグを使うのか疑問に思ったのがきっかけです。 1.1 <script> の属性なし / async / defer の違い 調べてみると、HTML の要素を探して操作する JS には、defer を付けるのが基本でした。ダウンロードは早く始めつつ、実行は HTML の解析が終わるまで待ってくれるからです。 <script src="a.js"></script> <!-- ① 属性なし --> <script src="a.js" async></script> <!-- ② async --> <script src="a.js" defer></script> <!-- ③ defer --> HTMLの解析を止めるか 実行タイミング 実行順 属性なし 止める 読み込んだらすぐ 書いた順 async ダウンロード中は止めない ダウンロードが終わりしだい 保証なし defer 止めない HTMLの解析が終わった後 書いた順 defer は「ダウンロードは早く、実行は最後」という良いとこ取り1 念のため、コード側でも DOMContentLoaded を待つと二重の保険になる querySelectorAll のように要素が見つからなくてもエラーにならない書き方では、何も起きないだけなので気づきにくい。defer を付けておけば、この「要素がまだ無い」事故を防げる 1.2 Webのルールは誰が決めているのか 調べてみると、Web のルールは1つの団体が決めているわけではなく、部分ごとに別の団体が決めていました。 パンくずリストを構造化データで書いたときの例で見てみます。 <script type="application/ld+json"> { "@context": "schema.org", "@type": "BreadcrumbList", "itemListElement": [ ... ] } </script> この数行でも、部分ごとにルールを決めている団体が違いました。 部分 ルール 決めている団体 <script> タグ HTML WHATWG application/ld+json MIMEタイプの仕組み / 登録 IETF / IANA { ... } の書き方 JSON IETF・Ecma International @context などの書き方 JSON-LD W3C BreadcrumbList という単語 語彙 schema.org WHATWG:HTML と DOM。ブラウザを作る企業が中心。2019年に W3C と合意し、HTML は「HTML Living Standard」に一本化。版の区切りはなく更新され続ける2 W3C:CSS、アクセシビリティ(WAI-ARIA)、JSON-LD など3 IETF:HTTP や JSON など、通信とデータ形式の基礎(RFC)4 IANA:MIMEタイプの一覧、ドメイン名、IPアドレスの登録・管理5 Ecma International:JavaScript の文法(ECMAScript)6 schema.org:Google・Microsoft・Yahoo! などによる共同プロジェクト。標準化団体ではなく、構造化データ用の単語帳を作っている7 ■ 補足 +json は「中身は JSON 形式」という意味の接尾辞8 <script> の type に JS 以外を指定すると、ブラウザは実行せずデータとして置いておく。検索エンジンはこれを読む 2. React と Islands Architecture Wikipedia のような辞書的なページは、全体を SPA にするとかえって重くなるので、一部分だけを React で動くようにすることがあると思います。こうした構成にも何か名前が付いているはずだと思い、調べてみたのがきっかけです。 2.1 「一部だけReact」すなわち Islands Architecture …ではなかった 最初は、一部だけ React を使う構成ならすべて Islands Architecture だと思っていましたが、サーバーが空の div を返し、ブラウザで React が中身を一から作る構成は、Islands Architecture ではありませんでした。Islands では島の HTML もサーバーが作り、ブラウザはそれを hydrate するからです。 ■ Islands Architecture の条件9 島(インタラクティブな部分)の初期 HTML もサーバーが描画する ブラウザはその HTML に hydrate(既存の要素を再利用して、イベントや状態を結び付ける)する 島の外には JS を送らない ■ 空の div にマウントする構成(React 17 の例) // サーバーが返すのは空の div だけ。ブラウザ側で中身を一から作る(render) document.querySelectorAll('.search-widget').forEach((el) => { // サーバーからの値は data-params 属性で受け取る ReactDOM.render(<SearchWidget params={JSON.parse(el.dataset.params)} />, el); }); サーバーが返す HTML と、JS 実行後の HTML を並べるとこうなります。 【render(空の div にマウントする構成)】 サーバーが返したHTML JS 実行後 <div class="search-widget"> <div class="search-widget"> (空っぽ) → <form>...</form> ← JS が一から作る </div> </div> 【hydrate(Islands Architecture)】 サーバーが返したHTML JS 実行後 <div> <div> <form>...</form> → <form>...</form> ← 要素は作り直さず、イベントや状態を結び付ける </div> </div> 見分け方:JS を切ったときに島の中身が見えるか React 17 なら ReactDOM.render が render、ReactDOM.hydrate が hydrate10 React 18 以降は、createRoot().render() と hydrateRoot() に置き換わっている11 「島の外には JS を送らない」点だけは Islands と共通 2.2 hydrate で起きていること 調べてみると、hydrate は、サーバーが作った要素をそのまま使い、クリック時の処理や状態を後から結び付ける処理でした。 サーバーから届いた HTML は「見た目」だけで、ボタンを押しても何も起きません。素の JS で、後から動くようにするとこうなります12。 <button id="like">いいね 0</button> // 既にあるボタンを探して、処理を後付けする(要素は作り直さない) const button = document.getElementById('like'); let count = 0; button.addEventListener('click', () => { count += 1; button.textContent = いいね ${count}; }); これが「既存の HTML にイベントを結び付ける」いちばん素朴な形 React の hydrate も考え方は同じで、中では次のことをしている サーバーが返したHTML(見た目だけ。押しても動かない) <form><input><button>検索</button></form> ↓ ReactDOM.hydrate(<SearchForm />, el) ① コンポーネントを実行して、「本来こうなるはず」の設計図をメモリ上に作る ② 設計図と既存の要素を先頭から照らし合わせ、一致した要素を「この部品の担当」として紐付ける ③ onClick などのイベント処理を受け付けられるようにする ④ useState の状態や useEffect を動かし始める ↓ 見た目は同じまま、押せば動く状態になる 設計図と HTML が食い違うと、React 17 は開発モードで警告を出す。テキストの違いは書き換えて合わせるが、属性の違いは直らないことがあるので、食い違いはバグとして直す10:1 ②で作る「設計図 ↔ 実際の要素」の対応表は、正確には Fiber というオブジェクトで、React はこれを使って書き換える要素や、押されたボタンの担当を見つけている13 ■ ブラウザで確かめる Fiber が実際の DOM 要素に付いている様子は、React で作られたサイトなら DevTools で見られます。ここでは React 公式サイトの react.dev を使います。 Chrome で react.dev を開き、DevTools を開く(Mac なら ⌘ + Option + I) DevTools の左上にある矢印のアイコン(要素を選ぶボタン)を押してから、「Learn React」ボタンをクリックする。矢印を押さずにクリックすると、リンク先へページが移動してしまう Console タブで $0(Elements タブで選択中の要素)を実行し、<a … aria-label="Learn React" …>Learn React</a> が表示されることを確かめる 次を実行する Object.keys($0) // => ['reactFiber$9s34k04vvrf', 'reactProps$9s34k04vvrf'] // (2026年10月時点。$ の後ろの文字列はページを開くたびに変わる) $0[Object.keys($0).find(k => k.startsWith('__reactProps$'))] // => {className: 'w-full sm:w-auto …', aria-label: 'Learn React', target: '_self', ref: ƒ, onClick: ƒ, …} reactFiber$… が Fiber への参照、reactProps$… が props。props の中に onClick の関数が入っているのが見える react.dev は React 18 以降で作られているが、キーの名前は React 17 と同じ これらは React 内部の仕組みで、公開された機能ではない。観察だけにとどめ、自分のコードから読み書きしない 2.3 render と hydrate の違いのまとめ 違うのは「要素を一から作るか、サーバーが作ったものを再利用するか」だけでした。Fiber との紐付け(2.2 の②)、イベントの受け付け(2.2 の③)、状態や副作用の開始(2.2 の④)は、どちらも同じです。 ここに「ページの一部か全体か」という範囲の軸を加えると、よく聞く構成は次の4つに整理できます。 render(要素を一から作る) hydrate(サーバーが作った要素を再利用) ページの一部 空の div にマウントする構成 Islands Architecture ページ全体 SPA サーバーで描画して hydrate(Next.js など) どのマスでも、マウントした要素より内側は Fiber と紐付く。違うのは「どこまでを React に任せるか」と「要素を作るか、再利用するか」だけ SPA もページ全体が Fiber と紐付くが、サーバーが返すのは空の div なので、hydrate ではなく render Islands との違いは hydrate する範囲で、Islands は島ごとに別々の React として hydrate し、島の外は React と無関係の HTML のまま。Next.js はページ全体(document)を1つの React として hydrate する 3. Rails Rails のチュートリアル系のサイトや動画で「コントローラーで @ を付けた変数は、ビューでも @ を付けて参照できる」と聞いたものの、裏で何が起きているのか分からず、Rails を学び始めてすぐに疑問に思ったのがきっかけで調べました。 3.1 コントローラーのインスタンス変数がビューに渡る仕組み コントローラーのインスタンス変数がビューで使えるのは、Rails がビュー用のオブジェクトに書き写しているからでした。さらに、テンプレートはそのオブジェクトのメソッドに変換されて実行されていました(Rails 8.1 のソースより)14。 class UsersController < ApplicationController def show @user = User.find(params[:id]) # @ 付きならビューで使える posts = @user.posts # @ なしはこのメソッドの中だけ end end 3.2 そもそも、なぜ書き写す必要があるのか インスタンス変数は、それを持っているオブジェクトの中からしか見えません15。 @user を持っているのは、UsersController のインスタンス テンプレートを実行するのは、それとは別のビュー用オブジェクト 別々のオブジェクトなので、ビューで @user を使うには、ビュー用オブジェクト側にも同じインスタンス変数が必要 そこで Rails が、コントローラーの変数を1つずつビュー用オブジェクトに書き写している 3.3 全体の流れ ブラウザから GET /users/1(ユーザーの詳細画面)のリクエストが届く ↓ ルーティング(resources :users)が users#show に振り分け、params[:id] = "1" にする ① コントローラーを new して、show アクションを実行する ↓ @user = User.find(1) ↓ render(書かなくても自動で呼ばれる) ② 集める view_assigns … コントローラーのインスタンス変数をハッシュにする ↓ { "user" => #<User id: 1> } ③ 作る view_context … ビュー用オブジェクトを new する(ハッシュとコントローラー自身を渡す) (new するクラスの正体は ④ で補足) ↓ ⑤ 書き込む assign … ハッシュの中身を、ビュー用オブジェクトのインスタンス変数にする ↓ ⑥ テンプレート実行 テンプレートを「ビュー用オブジェクトのメソッド」に変換して呼ぶ → テンプレートの @user は、ビュー用オブジェクトの @user 以下、順に見ていきます。 ① コントローラーはリクエストごとに new される ソースを読むと、Rails はリクエストが届くたびにコントローラーを new していました。ルーティングが users#show と判断すると、UsersController.dispatch というクラスメソッドが呼ばれ、その中で new されます(actionpack action_controller/metal.rb)。 def self.dispatch(name, req, res) ... new.dispatch(name, req, res) # UsersController.new して、そのインスタンスで処理する end Web サーバー(Puma など)がリクエストを受け取る ↓ Rails.application.call(env) Rack ミドルウェア(Rails の中で順番に呼ばれる前処理の部品。セッション・Cookie など) ↓ ActionDispatch::Routing::RouteSet#call … ルーティング ↓ Journey::Router#serve … GET /users/1 に合うルートを探す ↓ RouteSet::Dispatcher#serve … "users" → UsersController クラスに変換 UsersController.dispatch(:show, req, res) … Metal に定義されたクラスメソッド ↓ new.dispatch(:show, req, res) … self.new = UsersController.new UsersController のインスタンス#dispatch … 以降はこのインスタンスの上で動く Rack は、Ruby の Web サーバー(Puma など)とアプリ(Rails など)をつなぐ共通ルール。「call(env) を受け取って [ステータス, ヘッダー, 本文] を返す」という約束だけを決めている env は、リクエストの情報(URL、HTTP メソッド、ヘッダーなど)を詰めたハッシュ Puma・Rack ミドルウェア・Rails は別々のサーバーではなく、同じ1つのプロセスの中で動いている リクエストごとに別のインスタンスなので、別のユーザーのリクエストと @user が混ざることはない ② 集める:view_assigns actionpack abstract_controller/rendering.rb のメソッドです。 show の実行後に呼ばれたときの値を、コメントで書き込んでいます。 def view_assigns variables = instance_variables - _protected_ivars instance_variables => [:@_request, :@_response, :@_params, :@_routes, ..., :@user] ← Rails 内部用も混ざっている 配列の引き算で内部用を外す => [:@user] variables.each_with_object({}) do |name, hash| # name = :@user、hash は最初は空の {} hash[name.slice(1, name.length)] = instance_variable_get(name) # "@user" の2文字目以降 "user" をキーに、@user の値を詰める end => { "user" => #<User id: 1> } end 使っているのは、Ruby の標準メソッド2つだけでした16。 メソッド 何をするか instance_variables そのオブジェクトが持つインスタンス変数の名前を、シンボルの配列で返す instance_variable_get(名前) 名前を指定して、その変数の値を取り出す instance_variables はレシーバーを省略しているので、self.instance_variables と同じ。self は①で new されたコントローラーのインスタンス 除外する _protected_ivars は、@_params や @_request など、Rails 内部用の変数を並べた一覧 each_with_object({}) は、空のハッシュを用意して1件ずつ詰め、最後にそのハッシュを返すメソッド つまり、コントローラーの中身を一覧して、@ 付きの変数を「名前 → 値」のハッシュに変換しているだけでした。 ③ 作る:view_context actionview action_view/rendering.rb のメソッドです。 def view_context view_context_class.new(lookup_context, view_assigns, self) end 第1引数:どのテンプレートファイルを使うかを決める条件(探すフォルダ、言語、html / json などの形式) 第2引数:②で作ったハッシュ 第3引数:コントローラー自身(self) ■ self はなぜコントローラーなのか view_context は actionview の ActionView::Rendering モジュールに書かれていますが、それでも self はコントローラーでした。実行時の self は、「そのメソッドがどのファイルやモジュールに書かれているか」ではなく、「ドットの左側にいたオブジェクト(レシーバー)」で決まるからです17。 module Greeting def who = self end class UsersController include Greeting # Greeting のメソッドを、UsersController のインスタンスから呼べるようにする end c = UsersController.new c.who.equal?(c) # => true(レシーバーの c が self になる) UsersController.ancestors.first(2) # => [UsersController, Greeting] include は、メソッドをコピーするのではなく、メソッドを探す経路(ancestors)にモジュールを追加する c.who と呼ぶと、Ruby は UsersController → Greeting の順に who を探し、見つけたメソッドを c の上で実行する Rails の view_context も同じで、コントローラーに include されたモジュールのメソッドです。①で new したコントローラーのインスタンスから、レシーバーを省略して(self.〜 として)呼ばれ続けるので、self はずっとそのインスタンスのままです。そのため第3引数の self には、リクエストを処理しているコントローラー自身が渡されます。 ④ ビュー用のクラスは「無名クラス」 new しているクラス view_context_class は、ActionView::Base を継承した無名クラス(名前のないクラス)でした。 ■ 無名クラスとは Ruby では Class.new でクラスを作れます18。それを定数(大文字で始まる名前)に入れると名前が付き、入れなければ無名クラスになります。 Dog = Class.new { def hi = "hi" } dog = Class.new { def hi = "hi" } Dog.name # => "Dog" dog.name # => nil(中身は同じでも、ローカル変数に入れただけでは名前が付かない) dog.new.hi # => "hi"(変数 dog を通して、普通のクラスと同じように使える) 違うのは名前の有無だけで、継承・include・メソッド定義・new は普通のクラスと同じようにできる 無名クラスでも、使い回すには変数などに入れておく必要がある。ただし変数はクラスを入れておく箱にすぎず、変数名はクラスの名前にならない。箱は何個あってもよく入れ替えもできるので、クラス側には「自分がどの名前で呼ばれているか」が分からない。名前として記録されるのは、最初に入れた定数だけ Rails はビュー用の無名クラスを、コントローラーのクラス側に @view_context_class として入れてキャッシュし、③の view_context_class.new(...) で取り出して new している ■ ヘルパーが呼べる理由 Rails は、この無名クラスにルーティングのヘルパーとアプリのヘルパーを include しています(action_view/rendering.rb)。 Class.new(klass) do include routes.url_helpers(supports_path) # users_path など include helpers # app/helpers/ のメソッド end ビュー用オブジェクトはこのクラスのインスタンスなので、ビューで users_path や自作のヘルパーが呼べる ancestors をたどると、無名クラス → ヘルパーのモジュール → … → ActionView::Base の順にメソッドを探している ⑤ 書き込む:initialize と assign ③の new を呼ぶと、Ruby は次の2つを順に行います。 メモリ上に空のオブジェクトを作る そのオブジェクトの initialize を、new と同じ引数で自動的に呼ぶ action_view/rendering.rb view_context_class.new(lookup_context, view_assigns, self) ↓ ↓ ↓ action_view/base.rb ↓ ↓ ↓ def initialize(lookup_context, assigns, controller) @lookup_context = lookup_context ... assign_controller(controller) # @_controller にコントローラーを入れる ... assign(assigns) # 最後に、②のハッシュを書き込む end def assign(new_assigns) @_assigns = new_assigns new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } end assign は、②の view_assigns のちょうど逆の操作でした。 ② コントローラー側 ⑤ ビュー側 使うメソッド instance_variable_get instance_variable_set 名前の変換 :@user → "user" "user" → "@user" 結果 変数 → ハッシュ ハッシュ → 変数 instance_variable_set("@user", value) は、@user = value を、変数名を文字列で指定して書いたもの19 @user = value と書くと変数名はコードを書いた時点で決まるが、instance_variable_set なら実行中に決められる Rails は開発者がどんな名前の変数を作るか知らないので、ハッシュのキーから名前を組み立てて代入している 2つのオブジェクトの中身の移り変わりを図にすると、こうなります。 【コントローラー】 @_params, @user=#<User name: "山田"> (show を実行した直後) │ │ ② view_assigns:内部用を除いて、名前と値のハッシュにする ▼ { "user" => #<User name: "山田"> } │ │ ③ ビュー用のクラス.new(ハッシュ, self) ▼ 【ビュー用オブジェクト】 (まだ空っぽ) │ │ ⑤ initialize の中で │ assign_controller(self) … @_controller にコントローラーを入れる │ assign(ハッシュ) … "user" を "@user" にして instance_variable_set ▼ 【ビュー用オブジェクト】 @_controller, @user=#<User name: "山田"> │ │ ⑥ テンプレートを実行 ▼ <p>山田さん</p> ⑥ テンプレートは「ビュー用オブジェクトのメソッド」になる ここでいうテンプレートは、app/views/ 配下のファイル全部のことです。 種類 ファイル名の例 アクションのテンプレート users/show.html.erb partial(部品のテンプレート) users/_form.html.erb レイアウト layouts/application.html.erb _ で始まるのは partial だけで、どれも同じ仕組みで動きます。ここでは show.html.erb を例にします。 <p><%= @user.name %>さん</p> Rails はこのファイルを、次の Ruby のメソッドに変換していました。rails runner で ActionView::Template を作り、compiled_source(action_view/template.rb)を表示させた結果です。実際の出力は1行にまとまっているので、見やすく改行してコメントを足しています。 def _app_views_users_show_html_erb__1476774381686002440_15872(local_assigns, output_buffer, &_) @virtual_path = "users/show"; @output_buffer.safe_append='<p>'.freeze; # 固定の HTML はそのまま足す @output_buffer.append=( @user.name ); # <%= @user.name %> は、式の結果を(エスケープして)足す @output_buffer.safe_append='さん</p>'.freeze; @output_buffer # 組み立てた HTML を返す end 実行したコード rails runner で、テンプレートの中身を直接渡して変換させました。ファイルを作ったり DB を書き換えたりはしないので、どの Rails アプリでも試せます。 bundle exec rails runner ' template = ActionView::Template.new( "<p><%= @user.name %>さん</p>", # テンプレートの中身 "app/views/users/show.html.erb", # ファイルのパス(実在しなくてよい) ActionView::Template.registered_template_handler(:erb), locals: [], format: :html, virtual_path: "users/show" ) puts template.send(:compiled_source) # Ruby のメソッドに変換した結果を表示 ' compiled_source は外から呼べない private メソッドなので、send で呼んでいる メソッド名の末尾の数字は、実行するたびに変わる テンプレートは、ファイルのまま実行されるのではなく、1つの def に変換される メソッド名は、ファイルのパスと番号から自動で作られる。先頭の _ は他のメソッドと名前がぶつからないための接頭辞で、partial かどうかとは関係ない Haml でも、生成されるコードの中身が違うだけで仕組みは同じ ■ 変換から実行までの3段階 show.html.erb(ただのテキストファイル) │ 1. 変換:テンプレートエンジン(ERB / Haml)が "def ... end" という Ruby の「文字列」にする ▼ "def _app_views_users_show_html_erb...(local_assigns, output_buffer, &_) ... @user ... end" │ 2. 定義:module_eval で読み込ませる → ビュー用のクラスで使える本物のメソッドになる ▼ ビュー用のクラスのメソッド _app_views_users_show_html_erb... │ 3. 実行:ビュー用オブジェクト.public_send(:_app_views_users_show_html_erb__...) ▼ メソッドの中の self = ビュー用オブジェクト ├ @user → self の @user(⑤で assign した値) └ users_path → self のメソッド(④で include されたヘルパー) module_eval(文字列) は、文字列のコードをクラスの中に書いたものとして実行する。"def ... end" を渡せば、そのクラスにメソッドが追加される public_send(:foo) は、メソッド名をシンボルで指定して呼ぶメソッド。obj.public_send(:foo) は obj.foo と同じ テンプレートのメソッド名は実行中に自動で作られるので、obj._app_views_... のようにコードへ直接書いておけない。そのため、名前を値として受け取れる public_send で呼んでいる klass = Class.new klass.module_eval("def greet = 'こんにちは'") # 文字列のコードから greet メソッドを定義する obj = klass.new name = :greet # 呼びたいメソッドの名前を、値として持っている obj.public_send(name) # => "こんにちは"(obj.greet と同じ) 1と2は最初の1回だけ。2回目以降のリクエストは、定義済みのメソッドを呼ぶだけなので速い テンプレートの @user が読めるのは、Ruby に「メソッドの中の @xxx は、そのメソッドを呼ばれたオブジェクト(self)の変数を指す」というルールがあるから(③の self の話と同じ考え方で、つまり今回はビュー用オブジェクトの @user を指す) 3.4 この仕組みから分かること 書き写されるのは、オブジェクトへの参照でした。中身を丸ごと複製(ディープコピー)しているわけではありませんでした。 Ruby の変数に入っているのは、オブジェクトそのものではなく「そのオブジェクトがどこにあるか」を指す参照です。②⑤の書き写しは、この参照を受け渡しているだけで、オブジェクトを複製する処理(dup など)はどこにもありません。 ref = instance_variable_get(:@user) # ② コントローラーの @user から参照を取り出す hash["user"] = ref # ハッシュに入れる(複製しない) instance_variable_set("@user", ref) # ⑤ ビュー用オブジェクトの @user に入れる(複製しない) そのため、2つの @user は同じオブジェクトを指します。 [コントローラー] @user ──┐ ├──→ #<User name: "山田">(メモリ上に1つだけ) [ビュー用オブジェクト] @user ──┘ 現象 理由 ビューで @user = 別のもの と代入し直しても、コントローラー側は変わらない 変数は別々で、ビュー側の矢印だけが別のものを指し直すから ビューで @user.name = "x" と中身を変えると、コントローラー側から見ても変わる 2つの変数が同じオブジェクトを指しているから partial でも @user が使える partial も同じビュー用オブジェクトのメソッドとして実行されるから ビューでは params も使える ビュー用オブジェクトは params のデータを持たず、⑤で @_controller に入れたコントローラーの params を呼んでいるから(delegate による委譲) @usr と書き間違えてもエラーにならず、nil になる Ruby では、作っていないインスタンス変数を読むと nil が返るから 4. テスト いろいろなテストコードを読むうちに、FactoryBot には create だけでなく build / build_stubbed もあり、データの作り方が3つあると知り、なぜ使い分ける必要があるのか疑問に思ったのがきっかけで調べました。 4.1 FactoryBot の create / build / build_stubbed 調べてみると、DB から読み直す必要がないテストなら、create ではなく build_stubbed を使うと速くなると分かりました。テストデータを作る FactoryBot には、オブジェクトの作り方が3種類ありました20。 user = create(:user) # DB に保存する user = build(:user) # 保存しない(User.new と同じ状態) user = build_stubbed(:user) # 保存しないが、保存済みのふりをする DB への保存 id 向いているテスト 速さ create する 本物 画面表示や検索など、DB から読み直す処理 遅い build しない nil 保存前のバリデーションなど 速い build_stubbed しない 偽の id DB を使わないメソッド 最速 DB に触らない分だけ速いので、create が要らない場面では build / build_stubbed を選ぶ 関連も、既定では親と同じ作り方になる(build なら関連も保存されない) build_stubbed は、DB に保存しないまま、保存済みのように振る舞います21。 user = build_stubbed(:user) user.id # => 1001(偽の id) user.persisted? # => true(「DB に保存済みか」を聞くと、保存済みと答える) user.save # => RuntimeError(本当に DB に触ろうとするとエラー) persisted? の persist は「残り続ける」という意味。「DB に保存されて、プログラムが終わっても残る状態になったか(永続化されたか)」を聞くメソッド build_stubbed はこれに true を返すので、user_path(user) のように保存済みを前提にした URL の生成なども、DB なしでテストできる おわりに 最後まで読んでいただき、ありがとうございました。 脚注 MDN Web Docs「<script>: スクリプト要素」 ↩︎ WHATWG「HTML Living Standard」 ↩︎ W3C「JSON-LD 1.1」 ↩︎ IETF「RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format」 ↩︎ IANA「Media Types」 ↩︎ Ecma International「ECMAScript Language Specification(ECMA-262)」 ↩︎ schema.org「About Schema.org」 ↩︎ IETF「RFC 6839: Additional Media Type Structured Syntax Suffixes」 ↩︎ Jason Miller「Islands Architecture」 ↩︎ React(旧ドキュメント)「ReactDOM」 ↩︎ ↩︎ React「hydrateRoot」「createRoot」 ↩︎ MDN Web Docs「EventTarget: addEventListener() メソッド」 ↩︎ facebook/react v17.0.2「ReactDOMComponentTree.js」「ReactDOMHostConfig.js」 ↩︎ Rails ソースコード(8-1-stable)「actionpack/lib/abstract_controller/rendering.rb」「actionview/lib/action_view/rendering.rb」「actionview/lib/action_view/base.rb」「actionpack/lib/action_controller/metal.rb」「actionview/lib/action_view/helpers/controller_helper.rb」「actionview/lib/action_view/template.rb」 ↩︎ Ruby リファレンスマニュアル「変数と定数(インスタンス変数)」 ↩︎ Ruby リファレンスマニュアル「Object#instance_variables」「Object#instance_variable_get」 ↩︎ Ruby リファレンスマニュアル「変数と定数(擬似変数)」 ↩︎ Ruby リファレンスマニュアル「Class.new」 ↩︎ Ruby リファレンスマニュアル「Object#instance_variable_set」 ↩︎ factory_bot「Build strategies」 ↩︎ thoughtbot/factory_bot v6.5.5「lib/factory_bot/strategy/stub.rb」 ↩︎

  5. 自動でrole=alertはしなくなったんだ "fix(Banner): make live-region roles explicit" astryx#6630

  6. 12/ The Facebook SDK crash of May 2020: facebook-ios-sdk#1373

  7. Lifeguard Lifeguard is a static analyzer to detect Lazy Imports incompatibilities and ease the adoption overhead for Lazy Imports in Python. Facebook/lifeguard

  8. Lifeguard Lifeguard is a static analyzer to detect Lazy Imports incompatibilities and ease the adoption overhead for Lazy Imports in Python. Facebook/lifeguard

    6 likes · 1 repost · 999 views
  9. YouTube · · YouTube description · Rahul Bharati · facebook/react

    Open Source Doesn't Mean Free: Licenses Explained (MIT, Apache, GPL) Open source doesn't mean free. Every repo has a LICENSE file, and it decides what you're actually allowed to do with the code: use it, change it, sell it, or none of those. Here's how open source licenses work (MIT, BSD, Apache 2.0 and the GPL, explained simply), and why it matters more now that AI can rewrite and reverse engineer software in hours. Who this video is for: Developers, indie hackers and tech leads who build on open source packages or AI tools and have never really read a LICENSE file. What you'll understand by the end: What a license actually lets you do, the difference between permissive licenses (MIT, BSD, Apache 2.0) and copyleft (GPL), what you owe when you ship MIT code, and why running GPL code as a web service is a loophole. Who I am: I'm Rahul, a full-stack engineer with seven years of building production apps, now focused on AI engineering and AI security. On this channel I test AI tools on real projects and show what works, what it costs, and where it breaks. In this video: • Why a viral repo like REA (Reverse Engineer Anything) being MIT licensed doesn't give you rights to the apps you point it at • The chardet story: a Python library rewritten with AI and relicensed, and the original author's issue "No right to relicense this project" • Copyright by default: why a GitHub repo with no license is "read only" • Permissive licenses: MIT, BSD (how the PlayStation 4 is built on FreeBSD), and Apache 2.0's patent promise • The part everyone forgets: attribution and the "open source licenses" screen • Copyleft and the GPL explained: the Linksys WRT54G and how OpenWrt was born • The one word that matters most in the GPL, "distribute", and how the cloud walked right around it This is part 1. In part 2, we look at the licenses built to close the cloud loophole, the ones that call themselves open but aren't, and what "open" means for AI models. We also come back to chardet: if an AI rewrites someone's code from scratch, is it still their code? ⏱️ Chapters 0:00 The LICENSE file nobody reads 0:42 AI, vibe coding & REA (Reverse Engineer Anything) 2:09 The chardet relicensing fight 3:17 Copyright by default: no license, no permission 4:23 Permissive licenses: MIT & BSD 5:41 Apache 2.0 & the patent promise 6:58 Attribution: "keep my name on it" 7:43 Copyleft & the GPL explained 8:44 Linksys WRT54G & OpenWrt 9:15 "Distribute": the GPL's key word and the cloud loophole 10:38 Coming up in part 2 📚 Sources • REA, Reverse Engineer Anything (MIT): morluto/rea • chardet issue #327, "No right to relicense this project": chardet#327 • Simon Willison on the chardet relicensing (Mar 2026): simonwillison.net • MIT License: choosealicense.com • Apache License 2.0: choosealicense.com • GNU GPL v3: choosealicense.com • React's LICENSE (MIT): github.com/facebook/react/blob/main/LICENSE • Kubernetes' LICENSE (Apache 2.0): github.com/kubernetes/kubernetes/blob/master/LICE… • PlayStation 4 system software (Orbis OS, FreeBSD): en.wikipedia.org • Linksys WRT54G series: en.wikipedia.org • OpenWrt: en.wikipedia.org Not legal advice. Licenses and their interpretation can change; check the actual LICENSE file and, for anything that matters, a lawyer. 👉 Part 2 is next. Subscribe so you don't miss it. #OpenSource #GPL #FOSS

    148 views
  10. Hacker News · · mooreds · facebook/mcrouter

    Support Ukraine Banner from Mcrouter Readme

    2 points
Trending awards, every completed day and week
Gold
571st place finishes
Silver
482nd place finishes
Bronze
493rd place finishes
Awarded repositories
45Owned by facebook now

Repositories

Ordered by gold, then silver, then bronze.

  1. react-nativeBest: 1st by stars, 8,899 stars857
  2. popBest: 1st by stars, 5,331 stars832
  3. follyBest: 1st by stars, 527 stars424
  4. hermesBest: 1st by stars, 1,243 stars410
  5. prepackBest: 1st by stars, 6,917 stars313
  6. flowBest: 1st by stars, 2,742 stars301
  7. inferBest: 1st by stars, 2,257 stars301
  8. TweaksBest: 1st by stars, 1,587 stars301
  9. relayBest: 1st by stars, 1,552 stars220
  10. draft-jsBest: 1st by stars, 1,867 stars211
  11. PathPickerBest: 1st by stars, 754 stars211
  12. chiselBest: 1st by stars, 514 stars210
  13. lexicalBest: 1st by stars, 2,740 stars203
  14. frescoBest: 1st by forks, 226 forks151
  15. nuclideBest: 1st by stars, 848 stars121
  16. AsyncDisplayKitBest: 1st by stars, 733 stars120
  17. proxygenBest: 1st by stars, 779 stars120
  18. immutable-jsBest: 1st by stars, 908 stars112
  19. rocksdbBest: 1st by stars, 607 stars111
  20. stethoBest: 1st by stars, 626 stars111
  21. zstdBest: 1st by stars, 843 stars111
  22. libphenomBest: 1st by stars, 328 stars110
  23. jestBest: 1st by stars, 349 stars100
  24. redexBest: 1st by stars, 978 stars100
  25. saplingBest: 2nd by stars, 478 stars022
  26. xctoolBest: 2nd by stars, 893 stars021
  27. docusaurusBest: 2nd by stars, 454 stars012
  28. augmented-traffic-controlBest: 2nd by forks, 99 forks011
  29. hadoop-20Best: 2nd by forks, 34 forks011
  30. componentkitBest: 2nd by stars, 892 stars010
  31. huxleyBest: 2nd by stars, 660 stars010
  32. mention-botBest: 2nd by stars, 557 stars010
  33. prophetBest: 2nd by stars, 673 stars010
  34. pyre-checkBest: 2nd by stars, 451 stars010
  35. stylexBest: 2nd by stars, 1,741 stars010
  36. three20Best: 2nd by PR authors, 4 PR authors010
  37. watchmanBest: 2nd by stars, 293 stars010
  38. HaxlBest: 3rd by stars, 420 stars002
  39. hhvmBest: 3rd by stars, 353 stars002
  40. lithoBest: 3rd by stars, 1,086 stars002
  41. fb-floBest: 3rd by stars, 150 stars001
  42. flipperBest: 3rd by stars, 989 stars001
  43. KVOControllerBest: 3rd by stars, 230 stars001
  44. openzlBest: 3rd by stars, 442 stars001
  45. php-sdkBest: 3rd by forks, 7 forks001

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.