AfterPack змінює те, як ваш код читається, і зберігає те, що він робить. Обфускована збірка повертає ті самі значення, кидає помилки тих самих типів, виводить те саме й змінює сторінку так само, як збірка, яку ви їй передали. Ця сторінка описує цей контракт для рушія 0.2.3 і новіших: що залишається незмінним, як перевіряють кожен реліз і де результат навмисно відрізняється.
Що залишається незмінним
На кожному пресеті і з кожним сідом обфускована збірка зберігає:
- Результати й помилки. Функції повертають ті самі значення. Код, який кидав помилку, і далі кидає помилку того самого типу.
- Ефекти та їхній порядок. Вивід у консоль, зміни DOM, мережеві запити й записи у сховище відбуваються в тому самому порядку з тими самими значеннями.
- Асинхронний порядок.
async-функції, проміси й таймери завершуються в тому самому порядку. - Глобальна поверхня. Класичний скрипт визначає ті самі глобальні змінні, що й раніше, тож інші скрипти на сторінці й далі можуть викликати його функції та читати його змінні. ES-модуль нічого не додає до
windowі не перезаписує жодної глобальної змінної. Файл зimportчиexportчитається як модуль; про файл без них рушію повідомляє плагін фреймворку, CLI абоsourceType. - Правила мови, на які спирається ваш код. Помилки суворого режиму,
thisта отримувачі викликів, гетери й сетери, поля класів, арифметикаBigInt, а також області видимостіevalіwithповодяться так, як написано. - Імена, які читає ваш код. У файлі, який читає
this.constructor.nameабоnew.target.name, як це робить власний клас помилки, класи й конструктори зберігають імена, які ви написали, за винятками з розділу навмисних відмінностей.
Дві збірки з різними сідами відрізняються формою, але ніколи поведінкою.
Що змінюється за задумом
- Текст коду. Змінюються результат
fn.toString(), номери рядків і стовпців, а також імена у трасуванні стека. Щоб читати трасування стека з продакшену, зберігайте source map приватно. - Розмір і швидкість. Результат більший, а починаючи з
mediumвін виконує більше роботи на кожну операцію. Виміряні числа є на сторінці Продуктивність. - Код, що досліджує сам себе. Коли рушій знаходить код, який під час виконання читає власну структуру, наприклад
fn.toString(), він зупиняє збірку й називає шаблон. Підтвердьте шаблон черезreflection.allowабо залиште файл без змін черезpaths.exclude.
Як перевіряють кожен реліз
Починаючи з рушія 0.2.3, кожен реліз рушія перед виходом проходить семантичну перевірку. Вона запускає кожну програму двічі, у написаному й обфускованому вигляді, і порівнює, що робить кожен запуск: значення, що повертаються, кинуті помилки, вивід у консоль і порядок асинхронної роботи. Будь-яка відмінність блокує реліз, якщо це не одна з навмисних відмінностей, наведених нижче.
Перевірка запускає:
- Невеликі цільові програми, кожна з яких спрямована на одне правило мови, яке трансформатор коду може порушити: класи й приватні поля, замикання та області видимості, генератори, порядок
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. Якщо виконуваному коду не потрібні локальні змінні, викликайтеevalопосередковано,(0, eval)(code), або винесіть код, якому потрібенevalчиwith, в окремий файл.
До рушія 0.2.3
Рушій 0.2.2 і давніші не виконували цього контракту в деяких випадках, які тепер покриває перевірка: імена верхнього рівня класичних скриптів, спільні з іншими скриптами, ES-модулі без імпортів, об'єкти на кшталт дат усередині шаблонних літералів, функції, які використовують як конструктори, поля класів поверх успадкованих аксесорів, функції, що посилаються на самих себе, арифметика BigInt на medium і вище, eval і with, блок catch, що повторно оголошує свою змінну через var, порядок async, опціональні виклики в дужках, константні вирази ** та імена класів, які читають під час виконання. Перезберіть проєкт на рушії 0.2.3 або новішому; журнал змін перелічує кожне виправлення.
Повідомити про відмінність
Відмінність, якої немає на цій сторінці, є помилкою. Відкрийте issue з невеликим відтворенням на github.com/afterpack-dev/afterpack/issues. Доки її не виправлено, paths.exclude залишає відповідний файл без змін.
Далі
- Діагностика: кожен код, про який може повідомити збірка, і що з ним робити.
- Як працює AfterPack: що трансформації роблять із вашим кодом.
- Пресети: що застосовує кожен рівень.
- Конфігурація:
reflection.allow,paths.excludeта інші ключі, на які посилається ця сторінка.