Раскол кода ў Angular або як падзяліць кампаненты паміж лянівымі модулямі

Гэты артыкул дасць вам больш глыбокае разуменне таго, як Angular падзяліў ваш код на кавалкі.

Калі вы баіцеся высновы Angular CLI, паказанага вышэй, альбо вам цікава, як адбываецца расшчапленне кода на самай справе, то гэты пост для вас.

Раскол кода дазваляе падзяліць свой код на розныя пакеты, якія можна загрузіць па патрабаванні. Пры правільным выкарыстанні можа мець вялікі ўплыў на час загрузкі.

Змест

  1. Чаму я павінен клапаціцца?
  2. Кутняе расшчапленне кода CLI пад капотам
  3. Простае кутняе прыкладанне з лянівымі модулямі
  4. Як падзяліць кампаненты паміж лянівымі модулямі
  5. Выснова

Чаму я павінен клапаціцца?

Давайце ўявім, што вы пачалі зусім новы вуглавы праект. Вы прачыталі шмат рэсурсаў пра тое, як архітэктаваць вуглавы дадатак, якая адпаведная структура папак і што самае галоўнае, як захаваць вялікую прадукцыйнасць запуску.

Вы выбралі Angular CLI і стварылі модульнае прыкладанне з мноствам гультаяватых функцыянальных модуляў. І, вядома, вы стварылі агульны модуль, куды вы паклалі агульнапрынятыя дырэктывы, трубы і кампаненты.

Праз некаторы час вы злавілі сябе на думцы, што пасля таго, як ваш новы функцыянальны модуль патрабуе пэўнай функцыянальнасці ад іншых функцыянальных модуляў, вы, як правіла, пераносіце гэтую функцыянальнасць на гэты адзіны агульны модуль.

Дадатак развіваецца, і вы адразу заўважылі, што час яго запуску не адпавядае вашым чаканням (і, што самае галоўнае, вашаму кліенту).

Цяпер вы сумняваецеся ...

  • Калі я пакладу ўсе мае трубы, дырэктывы і агульныя кампаненты ў адзін вялікі агульны модуль, а потым імпортую яго ў ляніва загружаныя модулі (дзе я выкарыстоўваю толькі адну ці дзве імпартаваныя функцыі), гэта, верагодна, можа выклікаць дублікаты нявыкарыстанага кода ў выходных файлах .
  • З іншага боку, калі я падзялю агульныя функцыі на некалькі агульных модуляў і імпартую толькі тыя, якія патрэбныя кожнаму канкрэтнаму модулю, гэта знізіць памер майго прыкладання? Ці Angular робіць усе такія аптымізацыі па змаўчанні?

Давайце дэмістыфікаваць!

Кутняе расшчапленне кода CLI пад капотам

Як мы ўсе ведаем, бягучая версія Angular CLI выкарыстоўвае вэб-пакет для выканання пакетаў. Але нягледзячы на ​​гэта, webpack таксама нясе адказнасць за расшчапленне кода.

Такім чынам, давайце паглядзім, як гэта робіць webpack.

Webpack 4 прадставіў SplitChunksPlugin, які дазваляе нам вызначыць некаторыя эўрыстыкі, каб падзяліць модулі на кавалкі. Шмат хто скардзіцца, што падобная канфігурацыя здаецца загадкавай. І ў той жа час гэта самая цікавая частка расшчаплення кода.

Але перад тым, як ужыць аптымізацыю SplitChunksPlugin, webpack стварае новы кавалак:

  • для кожнай кропкі ўваходу

Вуглавы CLI наладжвае наступныя кропкі ўваходу

асноўныя стылі поліфілінгу

што прывядзе да кавалкаў з аднолькавымі назвамі.

  • для кожнага дынамічна загружанага модуля (з выкарыстаннем сінтаксісу import (), які адпавядае прапанове ECMAScript для дынамічнага імпарту)

Вы памятаеце сінтаксіс loadChildren? Гэта сігнал для webpack для стварэння кавалка.

Зараз пяройдзем да SplitChunksPlugin. Яго можна ўключыць у блоку аптымізацыі webpack.config.js

Давайце паглядзім на зыходны код AngI CLI і знойдзем гэты раздзел канфігурацыі:

Канфігурацыя SplitChunksPlugin у вуглавым CLI 8

Мы спынімся на варыянтах cacheGroups тут, бо гэта "рэцэпт" для вэб-пакета пра тое, як стварыць асобныя кавалкі на аснове некаторых умоў.

cacheGroups - гэта звычайны аб'ект, дзе ключ - імя групы. У асноўным мы можам разглядаць групу кэша як патэнцыйную магчымасць для стварэння новага кавалка.

Кожная група мае мноства канфігурацый і можа ўспадкаваць канфігурацыю ад узроўню splitChunks.

Давайце перагледзім тыя параметры, якія мы бачылі ў канфігурацыі Angular CLI вышэй:

  • Значэнне кавалкаў можа выкарыстоўвацца для фільтрацыі модуляў паміж кавалкамі сінхранізацыі і асінхранізацыі. Яго значэнне можа быць пачатковым, асінхронным альбо ўсім. зыходныя сродкі дадаюць файлы толькі ў кавалак, калі яны імпартаваныя ўнутры кавалкаў сінхранізацыі. Async азначае даданне файлаў у кавалак толькі ў тым выпадку, калі яны імпартаваны ўнутры кавалкаў async (па змаўчанні async)
  • minChunks паведамляе webpack ўводзіць толькі модулі, калі яны падзелены па меншай меры двума кавалкамі (па змаўчанні 1)
  • імя загадвае webpack выкарыстоўваць гэтае імя для навастворанага кавалка. Указанне альбо радка, альбо функцыі, якая заўсёды вяртае тую ж радок, аб'яднае ўсе агульныя модулі ў адзін фрагмент.
  • Значэнне прыярытэту выкарыстоўваецца для вызначэння найбольш падабраных кавалкаў, калі модуль трапляе пад мноства груп.
  • прымусіць загадваць webpack ігнараваць параметры minSize, minChunks, maxAsyncRequests і maxInitialRequests і заўсёды ствараць кавалкі для гэтай групы кэша. Тут ёсць адна невялікая сетка: калі які-небудзь з гэтых праігнараваных варыянтаў прадастаўляецца на ўзроўні cacheGroup, гэты параметр усё яшчэ будзе выкарыстоўвацца.
  • тэставае кіраванне, якія модулі абраны гэтай групай кэша. Як мы маглі заўважыць, Angular CLI выкарыстоўвае гэтую опцыю, каб перамясціць усе залежнасці ад модуляў node_module на кавалак пастаўшчыка.
  • minSize выкарыстоўваецца для ідэнтыфікацыі мінімальнага памеру ў байтах для стварэння кавалка. Ён не з'явіўся ў канфігурацыі Angular CLI, але гэта вельмі важны варыянт, пра які мы павінны ведаць. (Як паказвае зыходны код, гэта 30 кб па змаўчанні ў вытворчасці і 10 кб у асяроддзі распрацоўшчыкаў)
Савет: нягледзячы на ​​тое, што дакументацыя webpack вызначае значэнні па змаўчанні, я б спасылаўся на зыходны код webpack, каб знайсці дакладныя значэнні

Давайце ацанім тут: Angular CLI перамесціць модуль у:

  • кавалак пастаўшчыка, калі гэты модуль ідзе з каталога node_modules.
  • кавалак па змаўчанні, калі гэты модуль імпартаваны ў модуль async і падзяляецца па меншай меры на два модулі. Звярніце ўвагу, што тут магчымыя шматлікія кавалкі па змаўчанні. Я растлумачу, як пазней вэб-пакет генеруе імёны для гэтых кавалкаў.
  • звычайны кавалак, калі гэты модуль імпартаваны ў модуль async і падзяляецца па меншай меры на два модулі і не падпадае пад кавалак па змаўчанні (прывітанне прыярытэту), а таксама незалежна ад таго, якога памеру ён (дзякуючы опцыі theenforce)

Дастаткова тэорыі, давайце практыкавацца.

Простае кутняе прыкладанне з лянівымі модулямі

Каб растлумачыць працэс SplitChunksPlugin, мы пачнем з спрошчанай версіі прыкладання Angular:

прыкладанне ├── a (лянівы) │ └── a.component.ts │ └── a.module.ts │ ├── ab │ └── ab.component.ts │ └── ab.module.ts │ ├── b (лянівы) │ └── b.component.ts │ └── b.module.ts │ └── c (лянівы) │ └── c.component.ts │ └── c.module. ts │ └── cd │ └── cd.component.ts │ └── cd.module.ts │ └── d (лянівы) │ └── d.component.ts │ └── d.module.ts │ └── агульнае │ └── shared.module.ts │ └── app.component.ts └── app.module.ts

Тут a, b, c і d - лянівыя модулі, гэта значыць яны імпартуюцца з дапамогай сінтаксісу import ().

кампаненты a і b у сваіх шаблонах выкарыстоўваюць кампанент ab. c і d кампаненты выкарыстоўваюць кампанент cd.

Залежнасці паміж вуглавымі модулямі

Розніца паміж ab.module і cd.module заключаецца ў тым, што ab.module імпартуецца ў a.module і b.module, а cd.module імпартуецца ў shared.module.

Гэтая структура дакладна апісвае сумневы, якія мы хацелі дэмістыфікаваць. Давайце разбярэмся, дзе ў канчатковым выхадзе будуць модулі ab і cd.

Алгарытм

1) Алгарытм SplitChunksPlugin пачынаецца з дадання кожнаму раней створанаму кавалку індэкса.

кавалкі па індэксе

2) Затым ён пераключае ўсе модулі ў кампіляцыі, каб запоўніць карту chunkSetsInGraph. Гэты слоўнік паказвае, якія кавалкі маюць адзін і той жа код.

кавалакSetsInGraph

Напр., 1,2 галоўныя радкі з шматлікімі запоўненымі сродкамі азначаюць, што ёсць па меншай меры адзін модуль, які з'яўляецца ў два блокі: галоўны і поліфанітны.

a і b модулі маюць адзін і той жа код fromab-модуля, таму мы таксама можам заўважыць камбінацыю (4,5) вышэй.

3) Прайдзіце ўсе модулі і высветліце, ці магчыма стварыць новы кавалак для пэўнай cacheGroup.

3a) Перш за ўсё, webpack вызначае, ці можна дадаць модуль у пэўную cacheGroup, правяраючы ўласцівасць thechecheGroup.test.

тэсты ab.module

тэст па змаўчанні undefined => ок
агульны тэст нявызначаны => ок
функцыя праверкі пастаўшчыка => false

Група кэша па змаўчанні і агульная група кэша не вызначала ўласцівасць thetest, таму ён павінен перадаць яго. Група кэша пастаўшчыка вызначае функцыю, дзе ёсць фільтр, які ўключае толькі модулі з шляху thenode_modules.

Тэсты cd.module супадаюць.

3b) Цяпер прыйшоў час прайсціся па ўсіх кавалках.

Кожны модуль разумее, у якіх кавалках ён знаходзіцца (дзякуючы ўласцівасці module.chunksIterable).

ab.module імпартуецца ў два лянівыя кавалкі. Такім чынам, яго спалучэння (4), (5) і (4,5).

З іншага боку, cd.module імпартуецца толькі ў theshared модуль, гэта значыць, ён імпартуецца толькі ў асноўны кавалак. Яго спалучэнні толькі (1).

Затым спалучаюцца камбінацыі фільтраў па памеры minChunk:

калі (chunkCombination.size 

Паколькі ab.module мае камбінацыю (4,5), ён павінен прайсці гэтую праверку. Гэтага нельга сказаць пра cd.module. На гэты момант гэты модуль застаецца жыць у галоўным кавалку.

3c) Ёсць яшчэ адна праверка cacheGroup.chunkds (пачатковая, асінхронная або ўсе)

ab.module імпартуецца ўнутры кавалкаў async (ляніва загружаныя). Гэта менавіта тое, што патрабуюць стандартныя і звычайныя групы кэша. Такім чынам, ab.module дадаецца да двух новых магчымых кавалкаў (па змаўчанні і звычайных).

Я паабяцаў гэта раней, так што мы ідзем.

Як webpack генеруе імя для кавалка, створанага SplitChunksPlugin?

Спрошчаная версія можа быць прадстаўлена як

дзе:

  • groupName - назва групы (у нашым выпадку па змаўчанні)
  • ~ гэта па змаўчанніAutomaticNameDelimiter
  • chunkNames ставіцца да спісу ўсіх імёнаў фрагментаў, якія ўваходзяць у гэтую групу. Гэтае імя падобна на шлях fullPath, але замест касой рысы выкарыстоўваецца -.

Напрыклад, DD-модуль азначае, што мы маем файл d.module ў тэчцы d.

Такім чынам, маючы тое, што мы выкарыстоўваліimport ('./a / a.module') і імпарт ('./ b / b.module') мы атрымліваем

Структура імя кавалка па змаўчанні

Яшчэ адна рэч, якую варта адзначыць, гэта тое, што калі даўжыня імя кавалка дасягае 109 знакаў, webpack скарачае яго і дадае крыху хэша ў канцы.

Структура імя вялікага кавалка, які падзяляе код на некалькі лянівых модуляў

Мы гатовы запоўніць фрагментыInfoMap, які ведае ўсё пра ўсе магчымыя новыя кавалкі, а таксама ведае, з якіх модуляў ён павінен складацца і звязаныя з імі кавалкі, дзе гэтыя модулі знаходзяцца ў цяперашні час.

кавалкіInfoMap

Час фільтраваць магчымыя кавалкі

SplitChunksPlugin завесы над элементамі chunksInfoMap, каб знайсці найлепшы адпаведны запіс. Што гэта значыць?

Група кэшаў па змаўчанні мае прыярытэтнае значэнне 10, якое перашкаджае частаце (у якой толькі 5). Гэта азначае, што па змаўчанні гэта самая лепшая запіс, і яна павінна быць апрацавана ў першую чаргу.

Пасля таго, як усе іншыя патрабаванні выкананы, webpack выдаляе ўсе модулі кавалачка з іншых магчымых фрагментаў у слоўніку chunksInfoMap. Калі модуля не засталося, модуль выдаляецца

Такім чынам, па змаўчанні ~ aa-модуль ~ bb-модуль мае перавагу над агульным кавалкам. Апошні выдаляецца, бо ў ім ёсць адзін і той жа спіс модуляў.

Апошні, але не менш важны крок, - зрабіць некаторыя аптымізацыі (напрыклад, выдаліць дублікаты) і пераканацца, што ўсе патрабаванні, такія як maxSize, выконваюцца.

Увесь зыходны код SplitChunksPlugin можна знайсці тут

Мы выявілі, што webpack стварае кавалкі трыма рознымі спосабамі:

  • для кожнага запісу
  • для дынамічна загружаных модуляў
  • для агульнага кода пры дапамозе SplitChunksPlugin
Вуглавы вывад CLI па тыпу кавалкаў

Зараз вернемся да нашых сумневаў у тым, што лепшы спосаб захаваць агульны код.

Як падзяліць кампаненты паміж лянівымі модулямі

Як мы бачылі ў нашым простым вуглавым дадатку, webpack стварыў асобны кавалак для ab.module, але ўключыў cd.module ў асноўны кавалак.

Давайце падвядзем вынікі ключавых вывадаў з гэтай пасады:

  • Калі мы змесцім усе агульныя трубы, дырэктывы і звычайныя кампаненты ў адзін вялікі агульны модуль, а потым імпартуем яго ўсюды (ўнутры кавалкаў сінхранізацыі і асінхроннай працы), то гэты код будзе знаходзіцца ў нашым першасным галоўным фрагменце. Такім чынам, калі вы хочаце атрымаць дрэнную прадукцыйнасць першапачатковай нагрузкі, то гэта шлях.
  • З іншага боку, калі мы падзелім звычайны код на лянівыя модулі, то новы агульны фрагмент будзе створаны і загружаецца толькі ў тым выпадку, калі загружаны любы з гэтых лянівых модуляў. Гэта павінна палепшыць пачатковую нагрузку прыкладання. Але рабіце гэта з розумам, бо часам лепш змясціць невялікі код у адзін кавалак, які мае дадатковы запыт, неабходны для асобнай нагрузкі.

Выснова

Я спадзяюся, што зараз вы павінны дакладна зразумець выходны кут CLI і адрозніваць ўваходныя, дынамічныя і падзеленыя пры дапамозе кавалкаў SplitChunksPlugin.

Шчаслівае кадаванне!