# Що обфускований код зберігає незмінним (семантичний контракт)

Що з поведінки вашої програми зберігає результат AfterPack, як кожен реліз рушія перевіряють на реальному коді, і короткий перелік навмисних відмінностей.

Source: https://www.afterpack.dev/uk/docs/semantic-contract

AfterPack змінює те, як ваш код читається, і зберігає те, що він робить. Обфускована збірка повертає ті самі значення, кидає помилки тих самих типів, виводить те саме й змінює сторінку так само, як збірка, яку ви їй передали. Ця сторінка описує цей контракт для рушія 0.2.3 і новіших: що залишається незмінним, як перевіряють кожен реліз і де результат навмисно відрізняється.

## Що залишається незмінним

На кожному [пресеті](https://www.afterpack.dev/uk/docs/presets) і з кожним [сідом](https://www.afterpack.dev/uk/docs/config#seed) обфускована збірка зберігає:

- **Результати й помилки.** Функції повертають ті самі значення. Код, який кидав помилку, і далі кидає помилку того самого типу.
- **Ефекти та їхній порядок.** Вивід у консоль, зміни DOM, мережеві запити й записи у сховище відбуваються в тому самому порядку з тими самими значеннями.
- **Асинхронний порядок.** `async`-функції, проміси й таймери завершуються в тому самому порядку.
- **Глобальна поверхня.** Класичний скрипт визначає ті самі глобальні змінні, що й раніше, тож інші скрипти на сторінці й далі можуть викликати його функції та читати його змінні. ES-модуль нічого не додає до `window` і не перезаписує жодної глобальної змінної. Файл з `import` чи `export` читається як модуль; про файл без них рушію повідомляє плагін фреймворку, CLI або [`sourceType`](https://www.afterpack.dev/uk/docs/config#sourceType).
- **Правила мови, на які спирається ваш код.** Помилки суворого режиму, `this` та отримувачі викликів, гетери й сетери, поля класів, арифметика `BigInt`, а також області видимості `eval` і `with` поводяться так, як написано.
- **Імена, які читає ваш код.** У файлі, який читає `this.constructor.name` або `new.target.name`, як це робить власний клас помилки, класи й конструктори зберігають імена, які ви написали, за винятками з розділу [навмисних відмінностей](#deliberate-differences).

Дві збірки з різними сідами відрізняються формою, але ніколи поведінкою.

## Що змінюється за задумом

- **Текст коду.** Змінюються результат `fn.toString()`, номери рядків і стовпців, а також імена у трасуванні стека. Щоб читати трасування стека з продакшену, зберігайте [source map](https://www.afterpack.dev/uk/docs/config#sourceMap-enabled) приватно.
- **Розмір і швидкість.** Результат більший, а починаючи з `medium` він виконує більше роботи на кожну операцію. Виміряні числа є на сторінці [Продуктивність](https://www.afterpack.dev/uk/docs/performance).
- **Код, що досліджує сам себе.** Коли рушій знаходить код, який під час виконання читає власну структуру, наприклад `fn.toString()`, він зупиняє збірку й називає шаблон. Підтвердьте шаблон через [`reflection.allow`](https://www.afterpack.dev/uk/docs/config#reflection-allow) або залиште файл без змін через [`paths.exclude`](https://www.afterpack.dev/uk/docs/config#paths-exclude).

## Як перевіряють кожен реліз

Починаючи з рушія 0.2.3, кожен реліз рушія перед виходом проходить семантичну перевірку. Вона запускає кожну програму двічі, у написаному й обфускованому вигляді, і порівнює, що робить кожен запуск: значення, що повертаються, кинуті помилки, вивід у консоль і порядок асинхронної роботи. Будь-яка відмінність блокує реліз, якщо це не одна з [навмисних відмінностей](#deliberate-differences), наведених нижче.

Перевірка запускає:

- **Невеликі цільові програми**, кожна з яких спрямована на одне правило мови, яке трансформатор коду може порушити: класи й приватні поля, замикання та області видимості, генератори, порядок `async`, шаблонні літерали, `BigInt`, `eval` і `with`, суворий режим, мітки та `switch`. Кожен пресет, багато сідів, і нативний рушій, і рушій WebAssembly.
- **Реальні бібліотеки** закріплених версій, кожну з яких проганяє невеликий скрипт: dayjs, Moment.js і Luxon для дат, viem, ethers і @noble/curves для криптографії, що інтенсивно використовує `BigInt`, Comlink, `_.template` з lodash і get-intrinsic.
- **Сторінки в Chromium**, які завантажують кілька класичних скриптів по черзі, ES-модулі з імпортами й без них, вбудовані обробники подій і користувацькі елементи, а також застосунок Vue в одному чанку. Кожна сторінка має поводитися так само й залишати `window` точно таким, як оригінал: без нових глобальних змінних і без перезаписаних.

## Навмисні відмінності

Ці відмінності та імена класів, описані після таблиці, є єдиними, які допускає перевірка. Кожен рядок таблиці стосується коду, який підміняє вбудовані об'єкти, обгортає екземпляри в `Proxy`, викликає клас без `new` або читає повідомлення помилок рушія JavaScript, тож більшість застосунків із ними ніколи не стикається. Вони діють усередині класів, які переписує рушій, починаючи з `light`; `minify` залишає класи такими, як написано.

| Ваш код | Нативна поведінка | Обфускований результат |
|---|---|---|
| Визначає публічні поля класу на екземплярі, який є `Proxy` (повернутим із базового конструктора) або має такий у ланцюжку прототипів | Спрацьовує лише пастка `defineProperty` проксі | Для кожного поля також спрацьовує пастка `has`, а для поля, якого в об'єкта немає, ще й пастка `set` і все, куди вона переспрямовує |
| Викликає конструктор класу без `new` | Кидає `TypeError` ще до того, як виконуються значення параметрів за замовчуванням чи деструктуризація | Виконує значення параметрів за замовчуванням і деструктуризацію, а потім кидає той самий `TypeError` |
| Викликає приватний метод або `super.method()` після підміни `Function.prototype.call` чи на методі з власною властивістю `call` | Викликає метод напряму | Викликає його через підмінений `call` |
| Читає повідомлення `TypeError` від перевірки приватного поля, повторної ініціалізації приватного поля або виклику класу без `new` | Власне повідомлення рушія JavaScript | Власне повідомлення AfterPack з тим самим типом помилки |
| Підміняє `TypeError`, `Object`, `Object.defineProperty`, `Reflect.construct`, `Reflect.ownKeys`, `WeakMap` або `WeakSet` до завантаження файлу | Класи й далі використовують оригінальні вбудовані об'єкти | Переписані класи використовують підміну |

Імена класів і конструкторів відрізняються в кількох випадках, на будь-якому пресеті. Файл, який читає їх лише іншими способами, наприклад `err.constructor.name` або `const { name } = new.target`, отримує перейменовані імена. У файлі, який читає `this.constructor.name` або `new.target.name`, перейменоване ім'я все одно видно для класу, створеного як `new (class X {})()`, для анонімного класу, що отримує ім'я від параметра за замовчуванням, для функції, яку створюють через `new` у гілці `switch`, що стоїть перед гілкою з її оголошенням, і для функції, яку створюють лише через інше посилання, наприклад `new ns.F()`, псевдонім або `Reflect.construct`.

## Що залишається таким, як написано

Ці конструкції залишаються такими, як написано, скрізь, де точно переписати їх недешево. Код навколо них і далі перейменовується й кодується, за винятком коду навколо `with` і прямого `eval`.

- `async`-функції та `await`.
- `delete`, а також `typeof`, застосований до простого імені.
- Функція, у якій блок `catch (e)` оголошує `var e`.
- `let` або `const`, оголошені безпосередньо в гілці `switch`.
- Опціональний виклик чи тег, у якого функцію, що викликається, взято в дужки або вона є членом `super`: `(o?.m)?.()`, `` (o?.m)`x` ``, `super.m?.()`.
- Клас, який присвоює значення власному імені, викликає член `super` як опціональний виклик, тег або функцію в дужках, або має похідний конструктор без рівно одного виклику `super()` на верхньому рівні.
- Цикли на `minify` і `light`.
- Області видимості навколо інструкції `with` або прямого `eval`. Імена в кожній охопній області видимості, аж до верхнього рівня файлу, зберігають оригінальне написання, а глобальні змінні всередині тіла `with` залишаються такими, як написано. Збірка повідомляє про це кодом [`DIAG_DYNAMIC_SCOPE_NATIVE`](https://www.afterpack.dev/uk/docs/diagnostics#complete-reference). Якщо виконуваному коду не потрібні локальні змінні, викликайте `eval` опосередковано, `(0, eval)(code)`, або винесіть код, якому потрібен `eval` чи `with`, в окремий файл.

## До рушія 0.2.3

Рушій 0.2.2 і давніші не виконували цього контракту в деяких випадках, які тепер покриває перевірка: імена верхнього рівня класичних скриптів, спільні з іншими скриптами, ES-модулі без імпортів, об'єкти на кшталт дат усередині шаблонних літералів, функції, які використовують як конструктори, поля класів поверх успадкованих аксесорів, функції, що посилаються на самих себе, арифметика `BigInt` на `medium` і вище, `eval` і `with`, блок `catch`, що повторно оголошує свою змінну через `var`, порядок `async`, опціональні виклики в дужках, константні вирази `**` та імена класів, які читають під час виконання. Перезберіть проєкт на рушії 0.2.3 або новішому; [журнал змін](https://www.afterpack.dev/uk/changelog) перелічує кожне виправлення.

## Повідомити про відмінність

Відмінність, якої немає на цій сторінці, є помилкою. Відкрийте issue з невеликим відтворенням на [github.com/afterpack-dev/afterpack/issues](https://github.com/afterpack-dev/afterpack/issues). Доки її не виправлено, [`paths.exclude`](https://www.afterpack.dev/uk/docs/config#paths-exclude) залишає відповідний файл без змін.

## Далі

- [Діагностика](https://www.afterpack.dev/uk/docs/diagnostics): кожен код, про який може повідомити збірка, і що з ним робити.
- [Як працює AfterPack](https://www.afterpack.dev/uk/docs/concepts): що трансформації роблять із вашим кодом.
- [Пресети](https://www.afterpack.dev/uk/docs/presets): що застосовує кожен рівень.
- [Конфігурація](https://www.afterpack.dev/uk/docs/config): [`reflection.allow`](https://www.afterpack.dev/uk/docs/config#reflection-allow), [`paths.exclude`](https://www.afterpack.dev/uk/docs/config#paths-exclude) та інші ключі, на які посилається ця сторінка.
