Partial Ivy + Linker — как компилируются БИБЛИОТЕКИ
Контекст (зачем вообще два режима): 00-overview. Тут — детально.
Проблема, которую решает partial compilation
Ivy-инструкции меняются от версии к версии Angular. Мы это видели: в v14 было
ɵɵelementStart, в v21 — ɵɵdomElementStart; decls/vars, набор инструкций, формат
defineComponent — всё это внутренний контракт рантайма конкретной версии.
Если бы библиотека публиковалась в npm в full Ivy (готовые ɵɵdefineComponent),
то либа, собранная под Angular 17, могла бы не завестись на рантайме Angular 21 —
инструкции бы не совпали. Это «version lock»: пришлось бы перевыпускать все либы под
каждую версию Angular.
Решение: библиотека публикуется в стабильном промежуточном формате — partial declaration. А финальные инструкции под нужную версию генерит linker уже при сборке приложения.
Что именно публикует библиотека (partial)
ng-packagr собирает либу с compilationMode: 'partial'
(angularCompilerOptions в tsconfig). Тогда ngtsc для компонента вызывает НЕ
compileComponentFromMetadata (full), а compileDeclareComponentFromMetadata
(compiler/src/render3/partial/component.ts:66). На выходе вместо инструкций —
вызов-декларация:
// в опубликованном .js библиотеки (упрощённо):
static ɵcmp = ɵɵngDeclareComponent({
version: "21.0.3",
type: MyButton,
selector: "my-button",
ngImport: i0,
template: "<button class=\"x\">{{ label }}</button>", // ← ШАБЛОН СТРОКОЙ, не разобран!
isInline: true,
// + inputs/outputs/encapsulation/styles...
});
Ключевое отличие от full: шаблон лежит строкой, выражения НЕ разобраны, никаких
ɵɵdomElementStart/decls/vars. Это просто «слепок метаданных». Формат стабилен —
R3DeclareComponentMetadata (compiler/src/render3/partial/api.ts) меняется редко и
обратносовместимо.
Функции-декларации (по ним детектится partial-код): ɵɵngDeclareComponent,
ɵɵngDeclareDirective, ɵɵngDeclarePipe, ɵɵngDeclareInjectable,
ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclareFactory,
ɵɵngDeclareClassMetadata.
Что делает Linker при сборке твоего приложения
Когда ng build тянет такую либу из node_modules, в дело вступает linker —
это Babel-плагин (compiler-cli/linker/babel/src/babel_plugin.ts).
Шаги:
- Фильтр (дёшево).
needsLinking(path, source)(linker/src/file_linker/needs_linking.ts) — простоsource.includes('ɵɵngDeclare…'). Файлы без деклараций пропускаются без парсинга, ради скорости. - Выбор линкера.
PartialLinkerSelectorпо имени функции иversionвыбирает нужныйPartialLinker(для компонента —PartialComponentLinkerVersion1). - Линковка.
linkPartialDeclaration(partial_linkers/partial_component_linker_1.ts:80):Внутриconst meta = this.toR3ComponentMeta(metaObj, version); // восстановить R3ComponentMetadata return compileComponentFromMetadata(meta, constantPool, makeBindingParser());toR3ComponentMetaон берёт строку шаблона из декларации и снова зовётparseTemplate(...)(partial_component_linker_1.ts:107).
То есть linker заново входит в тот же full-pipeline (01-full-pipeline,
стадии 2→7): parseTemplate → ingest → фазы → reify → emit. Результат —
ɵɵngDeclareComponent({...}) заменяется в коде на `ɵɵdefineComponent({...}),
сгенерированный компилятором той версии Angular, которой собирается приложение.
библиотека (npm) приложение (ng build)
ɵɵngDeclareComponent({ Babel linker plugin:
version, type, 1. needsLinking? (includes "ɵɵngDeclare")
template: "<...строка>", 2. выбрать PartialComponentLinkerVersion1
inputs, ... 3. parseTemplate(строка) → compileComponentFromMetadata
}) ▼
ɵɵdefineComponent({ decls, vars, template: fn,... })
(инструкции версии ТВОЕГО Angular)
Важная деталь про версии: linker читает поле version из декларации и включает/
выключает фичи синтаксиса под неё (partial_component_linker_1.ts:99–105): напр.
блок-синтаксис @if/@for доступен только для деклараций v17+, @let — v18.1+. Так
один linker корректно дотягивает либы, собранные разными версиями.
Когда linker НЕ нужен
Если ты собираешь монорепу, где либы компилируются вместе с приложением (full с
самого начала) — partial/linker не участвует. Partial нужен именно для опубликованных
в npm пакетов, которые пришли уже в ɵɵngDeclare-форме. Твой собственный app
всегда идёт full-путём напрямую.
Итог
- Либы публикуются в partial (
ɵɵngDeclareComponent, шаблон строкой) — формат стабилен между версиями Angular, нет version-lock. - Linker (Babel-плагин) при
ng buildприложения находит декларации, заново зовётparseTemplate+compileComponentFromMetadataи подменяет их на полноценныеɵɵdefineComponentпод версию приложения. - Поэтому код либы и твой код в итоге дают одинаковые Ivy-инструкции — см. общую картину в 00-overview.
Привязка к исходникам
compiler/src/render3/partial/component.ts—compileDeclareComponentFromMetadata(что эмитит partial)compiler/src/render3/partial/api.ts—R3DeclareComponentMetadata(стабильный формат)compiler-cli/linker/src/file_linker/needs_linking.ts— дешёвый фильтрcompiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts— сама линковкаcompiler-cli/linker/babel/src/babel_plugin.ts— встраивание в Babel/сборку