Frankenstein Göçü: Çerçeve-Agnostik Yaklaşım (Bölüm 2)
Yayınlanan: 2022-03-10Bu makalede, önceki bölümdeki önerileri izleyerek bir uygulamanın adım adım geçişini gerçekleştirerek tüm teoriyi teste tabi tutacağız. İşleri basitleştirmek, belirsizlikleri, bilinmeyenleri ve gereksiz tahminleri azaltmak için, göçün pratik örneği için, uygulamayı basit bir yapılacaklar uygulamasında göstermeye karar verdim.

Genel olarak, genel bir yapılacaklar uygulamasının nasıl çalıştığını iyi anladığınızı varsayıyorum. Bu tür bir uygulama ihtiyaçlarımıza çok iyi uyuyor: öngörülebilir, ancak Frankenstein Migration'ın farklı yönlerini göstermek için gerekli minimum sayıda gerekli bileşene sahip. Ancak, gerçek uygulamanızın boyutu ve karmaşıklığı ne olursa olsun, yaklaşımın iyi ölçeklenebilir olması ve her büyüklükteki projeye uygun olması beklenir.

Bu makale için başlangıç noktası olarak TodoMVC projesinden bir jQuery uygulaması seçtim - bir çoğunuzun zaten aşina olduğu bir örnek. jQuery yeterince eskidir, projelerinizle ilgili gerçek bir durumu yansıtabilir ve en önemlisi, modern bir dinamik uygulamayı güçlendirmek için önemli bakım ve hack'ler gerektirir. (Bu, daha esnek bir şeye geçişi düşünmek için yeterli olmalıdır.)
O zaman göç edeceğimiz bu “daha esnek” nedir? Gerçek hayatta faydalı olabilecek oldukça pratik bir vakayı göstermek için bugünlerde en popüler iki çerçeveden birini seçmek zorunda kaldım: React ve Vue. Ancak, hangisini seçersem seçeyim, diğer yönün bazı yönlerini kaçırırdık.
Dolayısıyla bu bölümde, aşağıdakilerin her ikisinden de geçeceğiz:
- Bir jQuery uygulamasının React'e taşınması ve
- Bir jQuery uygulamasının Vue'ya taşınması.

Kod Depoları
Burada bahsedilen tüm kodlar herkese açıktır ve istediğiniz zaman erişebilirsiniz. Oynayabileceğiniz iki depo vardır:
- Frankenstein TodoMVC
Bu depo, farklı çerçevelerde/kütüphanelerde TodoMVC uygulamalarını içerir. Örneğin, bu depodavue
,angularjs
,react
vejquery
gibi dalları bulabilirsiniz. - Frankenstein Tanıtımı
Her biri, ilk depoda bulunan uygulamalar arasında belirli bir geçiş yönünü temsil eden birkaç dal içerir. Özellikle, daha sonra ele alacağımızmigration/jquery-to-react
vemigration/jquery-to-vue
gibi dallar vardır.
Her iki depo da devam eden bir çalışmadır ve bunlara düzenli olarak yeni uygulamalar ve geçiş yönergeleri içeren yeni şubeler eklenmelidir. ( Siz de katkıda bulunabilirsiniz! ) Göç dallarındaki taahhüt geçmişi iyi yapılandırılmıştır ve bu makalede ele alabileceğimden daha fazla ayrıntı içeren ek belgeler olarak hizmet edebilir.
Şimdi ellerimizi kirletelim! Önümüzde uzun bir yol var, bu yüzden sorunsuz bir yolculuk olmasını beklemeyin. Bu makaleyi nasıl takip etmek istediğinize karar vermek size kalmış, ancak aşağıdakileri yapabilirsiniz:
- Frankenstein TodoMVC deposundan
jquery
dalını klonlayın ve aşağıdaki tüm talimatları kesinlikle izleyin. - Alternatif olarak, Frankenstein Demo deposundan React'e geçiş veya Vue'ye geçiş için ayrılmış bir şube açabilir ve taahhüt geçmişi ile birlikte takip edebilirsiniz.
- Alternatif olarak, rahatlayabilir ve okumaya devam edebilirsiniz çünkü burada en kritik kodu vurgulayacağım ve asıl koddan ziyade sürecin mekaniğini anlamak çok daha önemli.
Yazının ilk teorik bölümünde anlatılan adımları harfiyen uygulayacağımızı bir kez daha belirtmek isterim.
Hemen dalalım!
- Mikro Hizmetleri Tanımlayın
- Ana Bilgisayardan Uzaylıya Erişime İzin Ver
- Yabancı Bir Mikro Hizmet/Bileşen Yaz
- Yabancı Hizmet Çevresine Web Bileşeni Sarıcı Yaz
- Ana Bilgisayar Hizmetini Web Bileşeniyle Değiştirin
- Tüm Bileşenleriniz İçin Durulayın ve Tekrarlayın
- Uzaylıya Geç
1. Mikro Hizmetleri Tanımlayın
Bölüm 1'in önerdiği gibi, bu adımda uygulamamızı belirli bir işe ayrılmış küçük , bağımsız hizmetler şeklinde yapılandırmamız gerekiyor. Dikkatli okuyucu, yapılacaklar uygulamamızın zaten küçük ve bağımsız olduğunu ve tek başına tek bir mikro hizmeti temsil edebileceğini fark edebilir. Bu uygulama daha geniş bir bağlamda yaşasaydı, kendime böyle davranırdım. Ancak, mikro hizmetleri tanımlama sürecinin tamamen öznel olduğunu ve tek bir doğru cevap olmadığını unutmayın.
Bu nedenle, Frankenstein Migration sürecini daha ayrıntılı görmek için bir adım daha ileri gidebilir ve bu yapılacaklar uygulamasını iki bağımsız mikro hizmete bölebiliriz:
- Yeni bir öğe eklemek için bir giriş alanı.
Bu hizmet, yalnızca bu öğelerin konum yakınlığına dayalı olarak uygulamanın başlığını da içerebilir. - Zaten eklenmiş öğelerin listesi.
Bu hizmet daha gelişmiştir ve listenin kendisiyle birlikte filtreleme, liste öğesinin eylemleri vb. gibi eylemleri de içerir.

İpucu : Seçilen hizmetlerin gerçekten bağımsız olup olmadığını kontrol etmek için, bu hizmetlerin her birini temsil eden HTML işaretlemesini kaldırın. Kalan işlevlerin hala çalıştığından emin olun. Bizim durumumuzda, liste olmadan giriş alanından localStorage
(bu uygulamanın depolama olarak kullandığı) yeni girişler eklemek mümkün olmalıdır , ancak liste giriş alanı eksik olsa bile localStorage
girişleri işlemeye devam eder. Potansiyel mikro hizmet için işaretlemeyi kaldırdığınızda uygulamanız hata veriyorsa, bu tür durumlarla nasıl başa çıkılacağına ilişkin bir örnek için Bölüm 1'deki "Gerekirse Yeniden Düzenleme" bölümüne bakın.
Tabii ki, devam edip ikinci hizmeti ve öğelerin listesini her bir özel öğe için bağımsız mikro hizmetlere bölebiliriz. Ancak, bu örnek için fazla ayrıntılı olabilir. Şimdilik uygulamamızın iki hizmeti olacağı sonucuna varıyoruz; bağımsızdırlar ve her biri kendi özel görevi için çalışır. Bu nedenle, uygulamamızı mikro hizmetlere ayırdık .
2. Ana Bilgisayardan Uzaylıya Erişime İzin Ver
Bunların ne olduğunu kısaca hatırlatayım.
- Ev sahibi
Mevcut uygulamamızın adı bu. Uzaklaşmak üzere olduğumuz çerçeve ile yazılmıştır. Bu özel durumda, jQuery uygulamamız. - Yabancı
Basitçe söylemek gerekirse, bu, taşınmak üzere olduğumuz yeni çerçeve üzerinde Host'un kademeli olarak yeniden yazılmasıdır. Yine, bu özel durumda, bu bir React veya Vue uygulamasıdır.
Host ve Alien'ı ayırırken temel kural, herhangi bir zamanda diğerini bozmadan herhangi birini geliştirip dağıtabilmeniz gerektiğidir.
Host ve Alien'ı birbirinden bağımsız tutmak Frankenstein Migration için çok önemlidir. Ancak bu, ikisi arasındaki iletişimi düzenlemeyi biraz zorlaştırıyor. İkisini birbirine çarpmadan Host'un Alien'a erişmesine nasıl izin veririz?
Uzaylıyı Sunucunuzun Alt Modülü Olarak Ekleme
İhtiyacımız olan kurulumu gerçekleştirmenin birkaç yolu olsa da, projenizi bu kriteri karşılayacak şekilde organize etmenin en basit şekli muhtemelen git alt modülleridir. Bu makalede kullanacağımız şey bu. Bu yapının sınırlamalarını ve kazanımlarını anlamak için git'teki alt modüllerin nasıl çalıştığını dikkatlice okumanızı size bırakacağım.
Git alt modülleri ile projemizin mimarisinin genel prensipleri şöyle görünmelidir:
- Hem Host hem de Alien bağımsızdır ve ayrı
git
depolarında tutulur; - Ana bilgisayar, Alien'e bir alt modül olarak başvurur. Bu aşamada, Host, Alien'ın belirli bir durumunu (taahhüt) seçer ve bunu, Host'un klasör yapısında bir alt klasör olarak göründüğü gibi ekler.

Bir alt modül ekleme işlemi, herhangi bir uygulama için aynıdır. git submodules
öğretmek bu makalenin kapsamı dışındadır ve doğrudan Frankenstein Migration'ın kendisiyle ilgili değildir. Öyleyse, olası örneklere kısaca bir göz atalım.
Aşağıdaki snippet'lerde örnek olarak React yönünü kullanıyoruz. Diğer herhangi bir geçiş yönü için, react
Frankenstein TodoMVC'den bir dalın adıyla değiştirin veya gerektiğinde özel değerlere ayarlayın.
Orijinal jQuery TodoMVC uygulamasını kullanarak devam ederseniz:
$ git submodule add -b react [email protected]:mishunov/frankenstein-todomvc.git react $ git submodule update --remote $ cd react $ npm i
Frankenstein Demo deposundan migration/jquery-to-react
(veya başka bir geçiş yönü) şubesiyle birlikte takip ederseniz, Alien uygulaması zaten orada git submodule
olarak olmalı ve ilgili klasörü görmelisiniz. Ancak, klasör varsayılan olarak boştur ve kayıtlı alt modülleri güncellemeniz ve başlatmanız gerekir.
Projenizin kökünden (Barındırıcınız):
$ git submodule update --init $ cd react $ npm i
Her iki durumda da Alien uygulaması için bağımlılıklar yüklediğimizi, ancak bunların alt klasöre korumalı alana alındığını ve Ana Bilgisayarımızı kirletmeyeceğini unutmayın.
Alien uygulamasını Host'unuzun bir alt modülü olarak ekledikten sonra, bağımsız (mikro servisler açısından) Alien ve Host uygulamalarına sahip olursunuz. Ancak, Host bu durumda Alien'ı bir alt klasör olarak kabul eder ve bu, Host'un Alien'a sorunsuz bir şekilde erişmesine izin verir.
3. Bir Uzaylı Mikro Servis/Bileşeni Yazın
Bu adımda öncelikle hangi microservice'in taşınacağına karar vermeli ve onu Alien tarafında yazmalı/kullanmalıyız. Adım 1'de belirlediğimiz hizmet sırasını takip edelim ve ilki ile başlayalım: yeni bir öğe eklemek için giriş alanı. Bununla birlikte, başlamadan önce, bu noktanın ötesinde, ön uç çerçevelerin öncüllerine doğru ilerlerken mikro hizmet veya hizmet yerine daha uygun bir terim bileşeni kullanacağımız konusunda hemfikir olalım ve bileşen terimi hemen hemen tüm modern tanımları takip eder. çerçeve.
Frankenstein TodoMVC deposunun dalları, Başlık bileşeni olarak ilk "Yeni öğe eklemek için giriş alanı" hizmetini temsil eden bir sonuç bileşeni içerir:
- React'te başlık bileşeni
- Vue'da başlık bileşeni
Bileşenleri seçtiğiniz çerçevede yazmak bu makalenin kapsamı dışındadır ve Frankenstein Migration'ın bir parçası değildir. Ancak, bir Alien bileşeni yazarken akılda tutulması gereken birkaç şey var.
Bağımsızlık
Her şeyden önce, Alien'daki bileşenler, daha önce Host tarafında kurulmuş olan aynı bağımsızlık ilkesini izlemelidir: bileşenler hiçbir şekilde diğer bileşenlere bağlı olmamalıdır.
birlikte çalışabilirlik
Hizmetlerin bağımsızlığı sayesinde, büyük olasılıkla Host'unuzdaki bileşenler, bir durum yönetim sistemi, bazı paylaşılan depolama yoluyla iletişim veya doğrudan bir DOM olayları sistemi aracılığıyla iyi kurulmuş bir şekilde iletişim kurar. Alien bileşenlerinin "birlikte çalışabilirliği", durum değişiklikleri hakkında bilgi göndermek ve diğer bileşenlerdeki değişiklikleri dinlemek için Host tarafından kurulan aynı iletişim kaynağına bağlanabilmeleri gerektiği anlamına gelir. Pratikte bu, Ana Bilgisayarınızdaki bileşenler DOM olayları aracılığıyla iletişim kurarsa, Alien bileşeninizi yalnızca durum yönetimini göz önünde bulundurarak oluşturmanın ne yazık ki bu tür bir geçiş için kusursuz çalışmadığı anlamına gelir.
Örnek olarak, jQuery bileşenlerimiz için birincil iletişim kanalı olan js/storage.js
dosyasına bir göz atın:
... fetch: function() { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); }, save: function(todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); var event = new CustomEvent("store-update", { detail: { todos } }); document.dispatchEvent(event); }, ...
Burada, yapılacaklar öğelerimizi depolamak için localStorage
kullanıyoruz (bu örnek güvenlik açısından kritik değildir) ve depolamadaki değişiklikler kaydedildikten sonra, document
öğesinde herhangi bir bileşenin dinleyebileceği özel bir DOM olayı göndeririz.
Aynı zamanda Alien tarafında (react diyelim) istediğimiz kadar karmaşık durum yönetimi iletişimi kurabiliriz. Ancak, bunu gelecek için saklamak muhtemelen akıllıca olacaktır: Alien React bileşenimizi Host'a başarılı bir şekilde entegre etmek için Host tarafından kullanılan aynı iletişim kanalına bağlanmamız gerekir. Bu durumda, localStorage
. İşleri basitleştirmek için, Host'un depolama dosyasını Alien'a kopyaladık ve bileşenlerimizi ona bağladık:
import todoStorage from "../storage"; class Header extends Component { constructor(props) { this.state = { todos: todoStorage.fetch() }; } componentDidMount() { document.addEventListener("store-update", this.updateTodos); } componentWillUnmount() { document.removeEventListener("store-update", this.updateTodos); } componentDidUpdate(prevProps, prevState) { if (prevState.todos !== this.state.todos) { todoStorage.save(this.state.todos); } } ... }
Artık Alien bileşenlerimiz, Host bileşenleriyle aynı dili konuşabilir ve bunun tersi de geçerlidir.
4. Yabancı Hizmet Çevresine Web Bileşeni Sarıcı Yazın
Şu anda sadece dördüncü adımda olmamıza rağmen, oldukça fazla şey başardık:
- Host uygulamamızı, Alien hizmetleriyle değiştirilmeye hazır bağımsız hizmetlere ayırdık;
- Host ve Alien'ı birbirinden tamamen bağımsız olacak, ancak
git submodules
aracılığıyla çok iyi bağlanmış olacak şekilde ayarladık; - Yeni çerçeveyi kullanarak ilk Alien bileşenimizi yazdık.
Şimdi yeni Alien bileşeninin Host'ta çalışabilmesi için Host ve Alien arasında bir köprü kurmanın zamanı geldi.
Bölüm 1'den Hatırlatma : Barındırıcınızın bir paket paketleyiciye sahip olduğundan emin olun. Bu makalede, Webpack'e güveniyoruz, ancak bu, tekniğin Rollup veya seçtiğiniz başka bir paketleyici ile çalışmayacağı anlamına gelmiyor. Ancak, eşlemeyi Webpack'ten deneylerinize bırakıyorum.
Adlandırma kuralı
Bir önceki makalede bahsedildiği gibi, Alien'ı Host'a entegre etmek için Web Bileşenlerini kullanacağız. Sunucu tarafında yeni bir dosya oluşturuyoruz: js/frankenstein-wrappers/Header-wrapper.js
. (Bu bizim ilk Frankenstein paketimiz olacak.) Paketleyicilerinizi Alien uygulamasındaki bileşenlerinizle aynı şekilde adlandırmanın iyi bir fikir olduğunu unutmayın, örneğin sadece bir “ -wrapper
” son eki ekleyerek. Bunun neden iyi bir fikir olduğunu daha sonra göreceksiniz, ancak şimdilik bunun, Alien bileşeninin Header.js
(React'te) veya Header.vue
(Vue'da) olarak adlandırılması durumunda, Ana bilgisayarın tarafı Header-wrapper.js
olarak adlandırılmalıdır.
İlk sarmalayıcımızda, özel bir öğeyi kaydetmek için temel ortak bilgi levhası ile başlıyoruz:
class FrankensteinWrapper extends HTMLElement {} customElements.define("frankenstein-header-wrapper", FrankensteinWrapper);
Ardından, bu öğe için Shadow DOM'u başlatmamız gerekiyor.
Shadow DOM'u neden kullandığımızı anlamak için lütfen Bölüm 1'e bakın.
class FrankensteinWrapper extends HTMLElement { connectedCallback() { this.attachShadow({ mode: "open" }); } }
Bununla, Web Bileşeninin tüm gerekli bitlerini kurduk ve Alien bileşenimizi karışıma eklemenin zamanı geldi. Her şeyden önce, Frankenstein sarmalayıcımızın başlangıcında, Alien bileşeninin oluşturulmasından sorumlu tüm bitleri içe aktarmalıyız.
import React from "../../react/node_modules/react"; import ReactDOM from "../../react/node_modules/react-dom"; import HeaderApp from "../../react/src/components/Header"; ...
Burada bir saniye ara vermemiz gerekiyor. Alien'in bağımlılıklarını node_modules
içe aktarmadığımızı unutmayın. Her şey react/
alt klasörde bulunan Alien'ın kendisinden gelir. Bu nedenle 2. Adım çok önemlidir ve Ev Sahibinin Alien varlıklarına tam erişime sahip olduğundan emin olmak çok önemlidir.
Artık Alien bileşenimizi Web Bileşeni'nin Gölge DOM'sinde oluşturabiliriz:
... connectedCallback() { ... ReactDOM.render(<HeaderApp />, this.shadowRoot); } ...
Not : Bu durumda React'in başka bir şeye ihtiyacı yoktur. Ancak, Vue bileşenini oluşturmak için, Vue bileşeninizi aşağıdaki gibi içerecek bir sarma düğümü eklemeniz gerekir:
... connectedCallback() { const mountPoint = document.createElement("div"); this.attachShadow({ mode: "open" }).appendChild(mountPoint); new Vue({ render: h => h(VueHeader) }).$mount(mountPoint); } ...
Bunun nedeni, React ve Vue'nin bileşenleri oluşturma biçimindeki farktır: React, bileşeni başvurulan DOM düğümüne eklerken Vue, başvurulan DOM düğümünü bileşenle değiştirir. Bu nedenle, Vue için .$mount(this.shadowRoot)
, esasen Shadow DOM'un yerini alır.
Şimdilik ambalajımıza yapmamız gereken tek şey bu. Frankenstein sarmalayıcısının hem jQuery-to-React hem de jQuery-to-Vue geçiş yönlerindeki mevcut sonucu burada bulunabilir:
- React bileşeni için Frankenstein Wrapper
- Vue bileşeni için Frankenstein Wrapper
Frankenstein paketinin mekaniğini özetlemek gerekirse:
- Özel bir öğe oluşturun,
- Gölge DOM'yi başlat,
- Bir Alien bileşeni oluşturmak için gereken her şeyi içe aktarın,
- Alien bileşenini özel öğenin Gölge DOM'sinde oluşturun.
Ancak bu, Alien in Host'umuzu otomatik olarak oluşturmaz. Mevcut Ana Bilgisayar işaretlemesini yeni Frankenstein sarmalayıcımızla değiştirmemiz gerekiyor.
Kemerlerinizi bağlayın, beklediğiniz kadar kolay olmayabilir!
5. Ana Bilgisayar Hizmetini Web Bileşeniyle Değiştirin
Devam edelim ve yeni Header-wrapper.js
dosyamızı index.html
ekleyelim ve mevcut başlık işaretlemesini yeni oluşturulan <frankenstein-header-wrapper>
özel öğesiyle değiştirelim.
... <!-- <header class="header">--> <!-- <h1>todos</h1>--> <!-- <input class="new-todo" placeholder="What needs to be done?" autofocus>--> <!-- </header>--> <frankenstein-header-wrapper></frankenstein-header-wrapper> ... <script type="module" src="js/frankenstein-wrappers/Header-wrapper.js"></script>
Ne yazık ki, bu bu kadar basit olmayacak. Bir tarayıcı açar ve konsolu kontrol ederseniz, sizi bekleyen Uncaught SyntaxError
vardır. Tarayıcıya ve ES6 modülleri için desteğine bağlı olarak, ya ES6 içe aktarmalarıyla ya da Alien bileşeninin oluşturulma şekliyle ilgili olacaktır. Her iki durumda da, bu konuda bir şeyler yapmalıyız, ancak sorun ve çözüm okuyucuların çoğu için tanıdık ve açık olmalıdır.
5.1. Gerektiğinde Webpack ve Babel'i güncelleyin
Frankenstein sarmalayıcımızı entegre etmeden önce biraz Webpack ve Babel büyüsü eklemeliyiz. Bu araçları düzenlemek makalenin kapsamı dışındadır, ancak Frankenstein Demo deposundaki ilgili taahhütlere bir göz atabilirsiniz:
- React'e geçiş için yapılandırma
- Vue'ya geçiş için yapılandırma
Esasen, dosyaların işlenmesini ve Webpack'in yapılandırmasında Frankenstein sarmalayıcılarıyla ilgili her şeyi tek bir yerde içerecek şekilde yeni bir frankenstein
giriş noktası kurduk.
Ana Bilgisayardaki Web Paketi, Alien bileşenini ve Web Bileşenlerini nasıl işleyeceğini öğrendiğinde, Ana Bilgisayarın işaretlemesini yeni Frankenstein sarmalayıcısıyla değiştirmeye hazırız.
5.2. Gerçek Bileşenin Değiştirilmesi
Bileşenin değiştirilmesi artık basit olmalıdır. Ana Bilgisayarınızın index.html
aşağıdakileri yapın:
-
<header class="header">
DOM öğesini<frankenstein-header-wrapper>
ile değiştirin; - Yeni bir komut dosyası ekleyin
frankenstein.js
. Bu, Frankenstein sarmalayıcılarıyla ilgili her şeyi içeren Webpack'teki yeni giriş noktasıdır.
... <!-- We replace <header class="header"> --> <frankenstein-header-wrapper></frankenstein-header-wrapper> ... <script src="./frankenstein.js"></script>
Bu kadar! Gerekirse sunucunuzu yeniden başlatın ve Host'a entegre Alien bileşeninin büyüsüne tanık olun.
Ancak yine de bir şeyler eksik gibiydi. Ana Bilgisayar bağlamındaki Alien bileşeni, bağımsız Alien uygulaması bağlamındaki gibi görünmüyor. Sadece stilsiz.

Neden böyle? Bileşenin stillerinin Alien bileşeniyle Host'a otomatik olarak entegre edilmesi gerekmez mi? Keşke olsalar ama birçok durumda olduğu gibi, duruma göre değişir. Frankenstein Migration'ın zorlu kısmına geliyoruz.
5.3. Uzaylı Bileşeninin Şekillendirilmesi Hakkında Genel Bilgiler
Her şeyden önce, ironi, işlerin çalışma biçiminde hiçbir hata olmamasıdır. Her şey çalışmak için tasarlandığı gibi. Bunu açıklamak için styling bileşenlerinin farklı yollarından kısaca bahsedelim.
Küresel Stiller
Bunları hepimiz biliyoruz: genel stiller belirli bir bileşen olmadan dağıtılabilir (ve genellikle dağıtılır) ve tüm sayfaya uygulanabilir. Global stiller, eşleşen seçicilerle tüm DOM düğümlerini etkiler.
Genel stillere birkaç örnek, index.html
dosyanızda bulunan <style>
ve <link rel="stylesheet">
etiketleridir. Alternatif olarak, tüm bileşenlerin de ona erişebilmesi için global bir stil sayfası bazı kök JS modüllerine aktarılabilir.
Uygulamaları bu şekilde şekillendirme sorunu açıktır: büyük uygulamalar için monolitik stil sayfalarını sürdürmek çok zor hale gelir. Ayrıca, önceki makalede gördüğümüz gibi, global stiller, React veya Vue'da olduğu gibi doğrudan ana DOM ağacında oluşturulan bileşenleri kolayca kırabilir.
Paketlenmiş Stiller
Bu stiller genellikle bir bileşenin kendisiyle sıkı bir şekilde birleştirilir ve nadiren bileşen olmadan dağıtılır. Stiller genellikle bileşenle aynı dosyada bulunur. Bu tür stilin iyi örnekleri, Vue.js'deki React veya CSS Modüllerinde stillendirilmiş bileşenler ve tek dosya bileşenlerinde Kapsamlı CSS'dir. Bununla birlikte, birleştirilmiş stiller yazmak için kullanılan araçların çeşitliliği ne olursa olsun, çoğunun altında yatan ilke aynıdır: araçlar, stillerin diğer bileşenleri veya globalleri bozmaması için bir bileşende tanımlanan stilleri kilitlemek için bir kapsam belirleme mekanizması sağlar. stiller.
Kapsamlı Stiller Neden Kırılgan Olabilir?
Bölüm 1'de, Frankenstein Migration'da Shadow DOM kullanımını gerekçelendirirken, kapsam belirlemeye karşı kapsülleme konusunu ve Shadow DOM kapsüllemenin kapsam belirleme stil araçlarından nasıl farklı olduğunu kısaca ele aldık. Bununla birlikte, kapsam belirleme araçlarının bileşenlerimiz için neden bu kadar kırılgan bir stil sağladığını açıklamadık ve şimdi, stilsiz Alien bileşeniyle karşılaştığımızda, anlamak için gerekli hale geliyor.
Modern çerçeveler için tüm kapsam belirleme araçları benzer şekilde çalışır:
- Kapsam veya kapsülleme hakkında fazla düşünmeden bileşeniniz için stiller yazarsınız;
- Bileşenlerinizi içe aktarılan/gömülü stil sayfaları ile Webpack veya Rollup gibi bazı paketleme sistemleri aracılığıyla çalıştırırsınız;
- Paketleyici, hem HTML'niz hem de ilgili stil sayfalarınız için ayrı seçiciler oluşturarak ve enjekte ederek benzersiz CSS sınıfları veya diğer nitelikler oluşturur;
- Paketleyici, belgenizin
<head>
bölümüne bir<style>
girişi yapar ve bileşenlerinizin stillerini benzersiz karışık seçicilerle oraya yerleştirir.
Hepsi bukadar. Çalışır ve birçok durumda iyi çalışır. Olmadığı durumlar dışında: tüm bileşenler için stiller global stil kapsamında yer aldığında, örneğin daha yüksek özgüllük kullanarak bunları kırmak kolaylaşır. Bu, kapsam belirleme araçlarının potansiyel kırılganlığını açıklıyor, ancak Alien bileşenimiz neden tamamen stilsiz?
DevTools kullanarak mevcut Host'a bir göz atalım. Örneğin, yeni eklenen Frankenstein paketini Alien React bileşeniyle incelerken şöyle bir şey görebiliriz:

Dolayısıyla Webpack, bileşenimiz için benzersiz CSS sınıfları oluşturur. Harika! O zaman stiller nerede? Eh, stiller tam olarak tasarlandıkları yerdedir - belgenin <head>
içinde.

<head>
içindedir. (Büyük önizleme) Yani her şey olması gerektiği gibi çalışıyor ve asıl sorun bu. Alien bileşenimiz Shadow DOM'da bulunduğundan ve Bölüm 1'de açıklandığı gibi, Shadow DOM, gölge sınırını geçemeyen bileşen için yeni oluşturulan stil sayfaları dahil olmak üzere sayfanın geri kalanından ve global stillerden bileşenlerin tam kapsüllenmesini sağlar ve Alien bileşenine gidin. Bu nedenle, Alien bileşeni stilsiz bırakılır. Ancak, şimdi, sorunu çözme taktikleri açık olmalıdır: bileşenin stillerini bir şekilde bileşenimizin bulunduğu Gölge DOM'ye yerleştirmeliyiz (belgenin <head>
yerine).
5.4. Uzaylı Bileşeni İçin Sabitleme Stilleri
Şimdiye kadar, herhangi bir çerçeveye geçiş süreci aynıydı. Bununla birlikte, burada işler farklılaşmaya başlar: her çerçevenin bileşenlerin nasıl şekillendirileceğine dair tavsiyeleri vardır ve bu nedenle sorunu çözme yolları farklıdır. Burada en yaygın durumları tartışıyoruz, ancak birlikte çalıştığınız çerçeve bazı benzersiz stil bileşenleri kullanıyorsa, bileşenin stillerini <head>
yerine Shadow DOM'a koymak gibi temel taktikleri aklınızda tutmanız gerekir.
Bu bölümde, aşağıdakiler için düzeltmeleri ele alıyoruz:
- Vue'da CSS Modülleri ile birlikte verilen stiller (Kapsamlı CSS taktikleri aynıdır);
- React'te stil bileşenleriyle birlikte verilen stiller;
- Genel CSS Modülleri ve genel stiller. Bunları birleştiriyorum çünkü CSS Modülleri genel olarak global stil sayfalarına çok benziyor ve stilleri herhangi bir bileşenden ayırarak herhangi bir bileşen tarafından içe aktarılabiliyor.
Önce kısıtlamalar: Stili düzeltmek için yaptığımız hiçbir şey Alien bileşeninin kendisini bozmamalıdır . Aksi takdirde Alien ve Host sistemlerimizin bağımsızlığını kaybederiz. Bu nedenle, stil sorununu çözmek için ya paketleyicinin konfigürasyonuna ya da Frankenstein sarıcısına güveneceğiz.
Vue ve Shadow DOM'da Paketlenmiş Stiller
Bir Vue uygulaması yazıyorsanız, büyük olasılıkla tek dosya bileşenleri kullanıyorsunuzdur. Ayrıca Webpack kullanıyorsanız, vue-loader
ve vue-style-loader
adlı iki yükleyiciye aşina olmalısınız. İlki, bu tek dosya bileşenlerini yazmanıza izin verirken, ikincisi bileşenin CSS'sini dinamik olarak bir <style>
etiketi olarak bir belgeye enjekte eder. Varsayılan olarak, vue-style-loader
bileşenin stillerini belgenin <head>
dosyasına enjekte eder. Bununla birlikte, her iki paket de varsayılan davranışı kolayca değiştirmemize ve (seçenek adından da anlaşılacağı gibi) stilleri Shadow DOM'a enjekte etmemize izin veren yapılandırmada shadowMode
seçeneğini kabul eder. Eylemde görelim.
Web Paketi Yapılandırması
En azından Web paketi yapılandırma dosyası aşağıdakileri içermelidir:
const VueLoaderPlugin = require('vue-loader/lib/plugin'); ... module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { shadowMode: true } }, { test: /\.css$/, include: path.resolve(__dirname, '../vue'), use: [ { loader:'vue-style-loader', options: { shadowMode: true } }, 'css-loader' ] } ], plugins: [ new VueLoaderPlugin() ] }
Gerçek bir uygulamada, test: /\.css$/
bloğunuz hem Host hem de Alien yapılandırmalarını hesaba katmak için (muhtemelen oneOf
kuralını içeren) daha karmaşık olacaktır. Bununla birlikte, bu durumda, jQuery'miz index.html
basit <link rel="stylesheet">
ile biçimlendirilmiştir, bu nedenle Web Paketi aracılığıyla Host için stiller oluşturmuyoruz ve yalnızca Alien için hizmet vermek güvenlidir.
Sarıcı Yapılandırması
Web paketi yapılandırmasına ek olarak, Vue'yi doğru Shadow DOM'a işaret ederek Frankenstein sarmalayıcımızı da güncellememiz gerekiyor. Header-wrapper.js
'mizde Vue bileşeninin oluşturulması, Frankenstein sarmalayıcımızın shadowRoot
giden shadowRoot
özelliğini içermelidir:
... new Vue({ shadowRoot: this.shadowRoot, render: h => h(VueHeader) }).$mount(mountPoint); ...
Dosyaları güncelledikten ve sunucunuzu yeniden başlattıktan sonra DevTools'unuzda şöyle bir şey alıyor olmalısınız:

Son olarak, Vue bileşeni için stiller Shadow DOM'umuz içindedir. Aynı zamanda, uygulamanız şöyle görünmelidir:

Vue uygulamamıza benzeyen bir şey almaya başlıyoruz: bileşenle birlikte verilen stiller, sarmalayıcının Gölge DOM'sine enjekte ediliyor, ancak bileşen hala olması gerektiği gibi görünmüyor. Bunun nedeni, orijinal Vue uygulamasında bileşenin yalnızca paketlenmiş stiller ile değil, aynı zamanda kısmen global stiller ile de stillendirilmesidir. Ancak, global stilleri düzeltmeden önce, React entegrasyonumuzu Vue ile aynı duruma getirmeliyiz.

React ve Shadow DOM'da Paketlenmiş Stiller
Bir React bileşenine stil vermenin birçok yolu olduğundan, Frankenstein Migration'da bir Alien bileşenini düzeltmenin özel çözümü, ilk etapta bileşeni stillendirme yöntemimize bağlıdır. En sık kullanılan alternatifleri kısaca ele alalım.
tarz bileşenler
styled-components, React bileşenlerini şekillendirmenin en popüler yollarından biridir. Header React bileşeni için, stilli-bileşenler tam olarak bizim onu biçimlendirme şeklimizdir. Bu klasik bir JS içinde CSS yaklaşımı olduğundan, örneğin .css
veya .js
dosyaları için yaptığımız gibi paketleyicimizi bağlayabileceğimiz özel bir uzantıya sahip bir dosya yoktur. Neyse ki, stilli bileşenler, StyleSheetManager
yardımcı bileşeninin yardımıyla belgenin head
yerine bileşenin stillerinin özel bir düğüme (bizim durumumuzda Gölge DOM) eklenmesine izin verir. "Stil bilgilerini enjekte etmek için alternatif bir DOM düğümü" tanımlayan, target
özelliğini kabul eden styled-components
paketi ile yüklenen önceden tanımlanmış bir bileşendir. Tam ihtiyacımız olan şey! Üstelik Webpack konfigürasyonumuzu değiştirmemize bile gerek yok: her şey Frankenstein sarmalayıcımıza bağlı.
React Alien bileşenini içeren Header-wrapper.js
aşağıdaki satırlarla güncellemeliyiz:
... import { StyleSheetManager } from "../../react/node_modules/styled-components"; ... const target = this.shadowRoot; ReactDOM.render( <StyleSheetManager target={target}> <HeaderApp /> </StyleSheetManager>, appWrapper ); ...
Burada StyleSheetManager
bileşenini içe aktarıyoruz (Host'tan değil Alien'dan) ve React bileşenimizi onunla sarıyoruz. Aynı zamanda, shadowRoot
işaret eden target
özelliğini gönderiyoruz. Bu kadar. Sunucuyu yeniden başlatırsanız, DevTools'unuzda şöyle bir şey görmeniz gerekir:

Artık bileşenimizin stilleri <head>
yerine Shadow DOM'da. Bu şekilde, uygulamamızın görüntüsü şimdi daha önce Vue uygulamasında gördüğümüze benziyor.

Aynı hikaye: styled-bileşenler, yalnızca React bileşeninin stillerinin paketlenmiş kısmından sorumludur ve genel stiller kalan bitleri yönetir. Bir tür şekillendirme bileşenini daha gözden geçirdikten sonra birazdan genel stillere geri döneceğiz.
CSS Modülleri
Daha önce düzelttiğimiz Vue bileşenine daha yakından bakarsanız, CSS Modüllerinin tam olarak bu bileşeni biçimlendirme şeklimiz olduğunu fark edebilirsiniz. However, even if we style it with Scoped CSS (another recommended way of styling Vue components) the way we fix our unstyled component doesn't change: it is still up to vue-loader
and vue-style-loader
to handle it through shadowMode: true
option.
When it comes to CSS Modules in React (or any other system using CSS Modules without any dedicated tools), things get a bit more complicated and less flexible, unfortunately.
Let's take a look at the same React component which we've just integrated, but this time styled with CSS Modules instead of styled-components. The main thing to note in this component is a separate import for stylesheet:
import styles from './Header.module.css'
The .module.css
extension is a standard way to tell React applications built with the create-react-app
utility that the imported stylesheet is a CSS Module. The stylesheet itself is very basic and does precisely the same our styled-components do.
Integrating CSS modules into a Frankenstein wrapper consists of two parts:
- Enabling CSS Modules in bundler,
- Pushing resulting stylesheet into Shadow DOM.
I believe the first point is trivial: all you need to do is set { modules: true }
for css-loader
in your Webpack configuration. Since, in this particular case, we have a dedicated extension for our CSS Modules ( .module.css
), we can have a dedicated configuration block for it under the general .css
configuration:
{ test: /\.css$/, oneOf: [ { test: /\.module\.css$/, use: [ ... { loader: 'css-loader', options: { modules: true, } } ] } ] }
Note : A modules
option for css-loader
is all we have to know about CSS Modules no matter whether it's React or any other system. When it comes to pushing resulting stylesheet into Shadow DOM, however, CSS Modules are no different from any other global stylesheet.
By now, we went through the ways of integrating bundled styles into Shadow DOM for the following conventional scenarios:
- Vue components, styled with CSS Modules. Dealing with Scoped CSS in Vue components won't be any different;
- React components, styled with styled-components;
- Components styled with raw CSS Modules (without dedicated tools like those in Vue). For these, we have enabled support for CSS modules in Webpack configuration.
However, our components still don't look as they are supposed to because their styles partially come from global styles . Those global styles do not come to our Frankenstein wrappers automatically. Moreover, you might get into a situation in which your Alien components are styled exclusively with global styles without any bundled styles whatsoever. So let's finally fix this side of the story.
Global Styles And Shadow DOM
Having your components styled with global styles is neither wrong nor bad per se: every project has its requirements and limitations. However, the best you can do for your components if they rely on some global styles is to pull those styles into the component itself. This way, you have proper easy-to-maintain self-contained components with bundled styles.
Nevertheless, it's not always possible or reasonable to do so: several components might share some styling, or your whole styling architecture could be built using global stylesheets that are split into the modular structure, and so on.
So having an opportunity to pull in global styles into our Frankenstein wrappers wherever it's required is essential for the success of this type of migration. Before we get to an example, keep in mind that this part is the same for pretty much any framework of your choice — be it React, Vue or anything else using global stylesheets!
Let's get back to our Header component from the Vue application. Take a look at this import:
import "todomvc-app-css/index.css";
This import is where we pull in the global stylesheet. In this case, we do it from the component itself. It's only one way of using global stylesheet to style your component, but it's not necessarily like this in your application.
Some parent module might add a global stylesheet like in our React application where we import index.css
only in index.js
, and then our components expect it to be available in the global scope. Your component's styling might even rely on a stylesheet, added with <style>
or <link>
to your index.html
. It doesn't matter. What matters, however, is that you should expect to either import global stylesheets in your Alien component (if it doesn't harm the Alien application) or explicitly in the Frankenstein wrapper. Otherwise, the wrapper would not know that the Alien component needs any stylesheet other than the ones already bundled with it.
Caution . If there are many global stylesheets to be shared between Alien components and you have a lot of such components, this might harm the performance of your Host application under the migration period.
Here is how import of a global stylesheet, required for the Header component, is done in Frankenstein wrapper for React component:
// we import directly from react/, not from Host import '../../react/node_modules/todomvc-app-css/index.css'
Nevertheless, by importing a stylesheet this way, we still bring the styles to the global scope of our Host, while what we need is to pull in the styles into our Shadow DOM. Bunu nasıl yapabiliriz?
Webpack configuration for global stylesheets & Shadow DOM
First of all, you might want to add an explicit test to make sure that we process only the stylesheets coming from our Alien. In case of our React migration, it will look similar to this:
test: /\.css$/, oneOf: [ // this matches stylesheets coming from /react/ subfolder { test: /\/react\//, use: [] }, ... ]
In case of Vue application, obviously, you change test: /\/react\//
with something like test: /\/vue\//
. Apart from that, the configuration will be the same for any framework. Next, let's specify the required loaders for this block.
... use: [ { loader: 'style-loader', options: { ... } }, 'css-loader' ]
Two things to note. First, you have to specify modules: true
in css-loader
's configuration if you're processing CSS Modules of your Alien application.
Second, we should convert styles into <style>
tag before injecting those into Shadow DOM. In the case of Webpack, for that, we use style-loader
. The default behavior for this loader is to insert styles into the document's head. Typically. And this is precisely what we don't want: our goal is to get stylesheets into Shadow DOM. However, in the same way we used target
property for styled-components in React or shadowMode
option for Vue components that allowed us to specify custom insertion point for our <style>
tags, regular style-loader
provides us with nearly same functionality for any stylesheet: the insert
configuration option is exactly what helps us achieve our primary goal. Harika haber! Let's add it to our configuration.
... { loader: 'style-loader', options: { insert: 'frankenstein-header-wrapper' } }
However, not everything is so smooth here with a couple of things to keep in mind.
Global stil sayfaları ve style-loader
insert
seçeneği
Bu seçeneğin belgelerini kontrol ederseniz, bu seçeneğin yapılandırma başına bir seçici aldığını fark edersiniz. Bu, bir Frankenstein sarmalayıcısına çekilen genel stiller gerektiren birkaç Alien bileşeniniz varsa, Frankenstein sarmalayıcılarının her biri için style-loader
belirtmeniz gerektiği anlamına gelir. Pratikte bu, muhtemelen tüm sarmalayıcılara hizmet vermek için yapılandırma bloğunuzdaki oneOf
kuralına güvenmeniz gerektiği anlamına gelir.
{ test: /\/react\//, oneOf: [ { test: /1-TEST-FOR-ALIEN-FILE-PATH$/, use: [ { loader: 'style-loader', options: { insert: '1-frankenstein-wrapper' } }, `css-loader` ] }, { test: /2-TEST-FOR-ALIEN-FILE-PATH$/, use: [ { loader: 'style-loader', options: { insert: '2-frankenstein-wrapper' } }, `css-loader` ] }, // etc. ], }
Çok esnek değil, katılıyorum. Yine de, taşınacak yüzlerce bileşeniniz olmadığı sürece önemli değil. Aksi takdirde, Webpack yapılandırmanızın bakımını zorlaştırabilir. Ancak asıl sorun, Shadow DOM için bir CSS seçici yazamıyor olmamızdır.
Bunu çözmeye çalışırken, insert
seçeneğinin, ekleme için daha gelişmiş mantık belirtmek için düz seçici yerine bir işlev alabileceğini de not edebiliriz. Bununla, stil sayfalarını doğrudan Shadow DOM'a eklemek için bu seçeneği kullanabiliriz! Basitleştirilmiş biçimde şuna benzer görünebilir:
insert: function(element) { var parent = document.querySelector('frankenstein-header-wrapper').shadowRoot; parent.insertBefore(element, parent.firstChild); }
Cazip, değil mi? Ancak, bu bizim senaryomuz için işe yaramayacak veya optimal olmaktan çok uzak olacak. <frankenstein-header-wrapper>
gerçekten de index.html
erişilebilir (çünkü onu Adım 5.2'de ekledik). Ancak Webpack, bir Alien bileşeni veya bir Frankenstein sarmalayıcısı için tüm bağımlılıkları (biçem sayfaları dahil) işlediğinde, Shadow DOM henüz Frankenstein sarmalayıcısında başlatılmaz: içe aktarmalar bundan önce işlenir. Bu nedenle, insert
doğrudan shadowRoot'a işaret etmek bir hataya neden olacaktır.
Webpack stil sayfası bağımlılığımızı işlemeden önce Shadow DOM'un başlatıldığını garanti edebileceğimiz tek bir durum vardır. Alien bileşeni bir stil sayfasının kendisini içe aktarmazsa ve içe aktarmak için Frankenstein sarmalayıcısına kalmışsa, Shadow DOM'u kurduktan sonra dinamik içe aktarmayı kullanabilir ve gerekli stil sayfasını içe aktarabiliriz:
this.attachShadow({ mode: "open" }); import('../vue/node_modules/todomvc-app-css/index.css');
Bu işe yarayacaktır: Yukarıdaki insert
yapılandırmasıyla birlikte bu tür bir içe aktarma, gerçekten de doğru Shadow DOM'u bulacak ve buna <style>
etiketini ekleyecektir. Bununla birlikte, stil sayfasının alınması ve işlenmesi zaman alacaktır, bu da, yavaş bağlantı veya yavaş aygıtlardaki kullanıcılarınızın, stil sayfanız sarmalayıcının Gölge DOM'sindeki yerine geçmeden önce, stili olmayan bileşenle bir an karşılaşabileceği anlamına gelir.

Sonuç olarak, insert
işlevi kabul etse de, ne yazık ki bu bizim için yeterli değil ve frankenstein-header-wrapper
gibi düz CSS seçicilerine geri dönmemiz gerekiyor. Ancak bu, stil sayfalarını otomatik olarak Shadow DOM'a yerleştirmez ve stil sayfaları Shadow DOM'un dışında <frankenstein-header-wrapper>
içinde bulunur.

style-loader
, içe aktarılan stil sayfasını Frankenstein sarmalayıcısına, ancak Gölge DOM'nin dışına yerleştirir. (Büyük önizleme)Yapbozun bir parçasına daha ihtiyacımız var.
Global stil sayfaları ve Shadow DOM için sarmalayıcı yapılandırması
Neyse ki, sarmalayıcı tarafında düzeltme oldukça basittir: Shadow DOM başlatıldığında, mevcut sarmalayıcıda bekleyen stil sayfalarını kontrol etmemiz ve bunları Shadow DOM'a çekmemiz gerekir.
Global stil sayfasının içe aktarımının mevcut durumu aşağıdaki gibidir:
- Shadow DOM'a eklenmesi gereken bir stil sayfasını içe aktarıyoruz. Stil sayfası, Alien bileşeninin kendisine veya açıkça Frankenstein sarmalayıcısına aktarılabilir. Örneğin, React'e geçiş durumunda, içe aktarma sarmalayıcıdan başlatılır. Ancak, Vue'ya geçişte, benzer bileşenin kendisi gerekli stil sayfasını içe aktarır ve sarmalayıcıda hiçbir şeyi içe aktarmamız gerekmez.
- Yukarıda belirtildiği gibi, Webpack Alien bileşeni için
.css
içe aktarmalarını işlerken,style-loader
insert
seçeneği sayesinde stil sayfaları bir Frankenstein sarmalayıcısına, ancak Gölge DOM'nin dışında enjekte edilir.
Frankenstein sarmalayıcısında Shadow DOM'un basitleştirilmiş başlatılması, şu anda (herhangi bir stil sayfası çekmeden önce) şuna benzer görünmelidir:
this.attachShadow({ mode: "open" }); ReactDOM.render(); // or `new Vue()`
Şimdi, stili uygulanmamış bileşenin titremesini önlemek için, şimdi yapmamız gereken, Shadow DOM'nin başlatılmasından sonra , ancak Alien bileşeninin oluşturulmasından önce gerekli tüm stil sayfalarını çekmek.
this.attachShadow({ mode: "open" }); Array.prototype.slice .call(this.querySelectorAll("style")) .forEach(style => { this.shadowRoot.prepend(style); }); ReactDOM.render(); // or new Vue({})
Çok fazla ayrıntı içeren uzun bir açıklamaydı, ancak esas olarak küresel stil sayfalarını Shadow DOM'a çekmek için gereken her şey:
- Web paketi yapılandırmasında, gerekli Frankenstein sarmalayıcısına işaret eden
insert
seçeneğiylestyle-loader
ekleyin. - Sargının kendisinde, Shadow DOM başlatıldıktan sonra, ancak Alien bileşeninin oluşturulmasından önce "beklemede" stil sayfalarını çekin.
Bu değişiklikleri uyguladıktan sonra, bileşeniniz ihtiyaç duyduğu her şeye sahip olmalıdır. Eklemek isteyebileceğiniz tek şey (bu bir gereklilik değildir), Host ortamında bir Alien bileşenine ince ayar yapmak için bazı özel CSS'lerdir. Hatta Alien bileşeninize Host'ta kullanıldığında tamamen farklı bir stil verebilirsiniz. Makalenin ana noktasının ötesine geçer, ancak sarmalayıcı düzeyinde basit stillerin nasıl geçersiz kılınacağına ilişkin örnekleri bulabileceğiniz sarmalayıcının son koduna bakarsınız.
- React bileşeni için Frankenstein sarmalayıcı
- Vue bileşeni için Frankenstein sarmalayıcı
Bu geçiş adımında Web paketi yapılandırmasına da göz atabilirsiniz:
- Tarz bileşenleriyle React'e Geçiş
- CSS Modülleriyle React'e Geçiş
- Vue'ye Geçiş
Ve son olarak, bileşenlerimiz tam olarak nasıl görünmelerini istediğimiz gibi görünüyor.

5.5. Uzaylı bileşeni için sabitleme stillerinin özeti
Bu, şimdiye kadar bu bölümde öğrendiklerimizi özetlemek için harika bir andır. Alien bileşeninin stilini düzeltmek için muazzam bir çalışma yapmamız gerekmiş gibi görünebilir; ancak, hepsi aşağı kaynar:
- React veya CSS modüllerinde stilli bileşenler ve Vue'da Kapsamlı CSS ile uygulanan paketlenmiş stilleri düzeltmek, Frankenstein sarmalayıcı veya Web paketi yapılandırmasında birkaç satır kadar basittir.
- CSS Modülleri ile uygulanan stilleri sabitleme,
css-loader
konfigürasyonunda sadece bir satırla başlar. Bundan sonra, CSS Modülleri global bir stil sayfası olarak ele alınır. - Global stil sayfalarını düzeltmek, Webpack'te
insert
seçeneğiylestyle-loader
paketini yapılandırmayı ve sarıcının yaşam döngüsünün doğru anında stil sayfalarını Shadow DOM'a çekmek için Frankenstein sarıcısını güncellemeyi gerektirir.
Sonuçta, Ana Bilgisayara taşınan uygun şekilde tasarlanmış Alien bileşenine sahibiz. Bununla birlikte, hangi çerçeveye geçiş yaptığınıza bağlı olarak sizi rahatsız edebilecek veya etmeyebilecek tek bir şey var.
Önce iyi haber: Vue'ya geçiş yapıyorsanız , demo gayet iyi çalışıyor olmalı ve taşınan Vue bileşeninden yeni yapılacaklar öğeleri ekleyebilmelisiniz. Ancak, React'e geçiş yapıyorsanız ve yeni bir yapılacaklar öğesi eklemeye çalışıyorsanız başarılı olamazsınız. Yeni öğeler eklemek işe yaramaz ve listeye hiçbir giriş eklenmez. Ama neden? Sorun ne? Önyargı yok, ancak React'in bazı şeyler hakkında kendi fikirleri var.
5.6. Shadow DOM'da React ve JS Olayları
React belgeleri size ne söylerse söylesin, React Web Bileşenleri için pek kolay değildir. Belgelerdeki örneğin basitliği herhangi bir eleştiriye dayanmaz ve Web Bileşeninde bir bağlantı oluşturmaktan daha karmaşık bir şey biraz araştırma ve inceleme gerektirir.
Alien bileşenimiz için stili düzeltirken gördüğünüz gibi, her şeyin Web Bileşenlerine neredeyse kutudan çıktığı Vue'nun aksine, React Web Bileşenleri için hazır değildir. Şimdilik, Web Bileşenleri içinde React bileşenlerinin en azından nasıl iyi görünmesini sağlayacağımızı biliyoruz, ancak düzeltilmesi gereken işlevsellik ve JavaScript olayları da var.
Uzun lafın kısası: Shadow DOM, olayları kapsüller ve onları yeniden hedeflerken, React, Shadow DOM'un bu davranışını doğal olarak desteklemez ve bu nedenle Shadow DOM içinden gelen olayları yakalamaz. Bu davranışın daha derin nedenleri var ve daha fazla ayrıntıya ve tartışmaya dalmak istiyorsanız React'in hata izleyicisinde açık bir sorun bile var.
Neyse ki akıllı insanlar bizim için bir çözüm hazırladı. @josephnvu çözümün temelini sağladı ve Lukas Bombach onu react-shadow-dom-retarget-events
npm modülüne dönüştürdü. Böylece paketi kurabilir, paketler sayfasındaki talimatları takip edebilir, paketleyicinizin kodunu güncelleyebilirsiniz ve Alien bileşeniniz sihirli bir şekilde çalışmaya başlayacaktır:
import retargetEvents from 'react-shadow-dom-retarget-events'; ... ReactDOM.render( ... ); retargetEvents(this.shadowRoot);
Daha performanslı olmasını istiyorsanız, paketin yerel bir kopyasını oluşturabilir (MIT lisansı buna izin verir) ve Frankenstein Demo deposunda yapıldığı gibi dinlenecek olay sayısını sınırlayabilirsiniz. Bu örnek için, hangi olayları yeniden hedeflemem gerektiğini biliyorum ve yalnızca bunları belirtin.
Bununla, nihayet (uzun bir süreç olduğunu biliyorum) ilk tarz ve tam işlevli Alien bileşeninin uygun şekilde taşınmasını tamamladık. Kendine iyi bir içki al. Hakediyorsun!
6. Tüm Bileşenleriniz İçin Durulayın ve Tekrarlayın
İlk bileşeni taşıdıktan sonra, tüm bileşenlerimiz için işlemi tekrarlamalıyız. Frankenstein Demosu söz konusu olduğunda, yalnızca bir tane kaldı: yapılacaklar listesinin oluşturulmasından sorumlu olan.
Yeni Bileşenler İçin Yeni Paketleyiciler
Yeni bir sarmalayıcı ekleyerek başlayalım. Yukarıda tartışılan adlandırma kuralına göre (React bileşenimiz MainSection.js
olarak adlandırıldığından), React'e geçişte karşılık gelen sarmalayıcı MainSection-wrapper.js
olarak adlandırılmalıdır. Aynı zamanda, Vue'daki benzer bir bileşen Listing.vue
olarak adlandırılır, bu nedenle Vue'ye geçişte karşılık gelen sarmalayıcı Listing-wrapper.js
olarak adlandırılmalıdır. Ancak, adlandırma kuralı ne olursa olsun, sarmalayıcının kendisi zaten sahip olduğumuzla neredeyse aynı olacaktır:
- React listeleme için sarmalayıcı
- Vue listesi için sarmalayıcı
React uygulamasındaki bu ikinci bileşende tanıttığımız ilginç bir şey var. Bazen, bu veya başka bir nedenle, bileşenlerinizde bir jQuery eklentisi kullanmak isteyebilirsiniz. React bileşenimiz için iki şeyi tanıttık:
- Bootstrap'ten jQuery kullanan araç ipucu eklentisi,
-
.addClass()
ve.removeClass()
gibi CSS sınıfları için bir geçiş.
Not : Sınıfları eklemek/kaldırmak için jQuery'nin bu kullanımı tamamen açıklayıcıdır. Lütfen bu senaryo için gerçek projelerde jQuery kullanmayın - bunun yerine düz JavaScript'e güvenin.
Elbette, jQuery'den geçiş yaptığımızda bir Alien bileşeninde jQuery'yi eklemek garip görünebilir, ancak bu örnekte Host'unuz Host'tan farklı olabilir - AngularJS'den veya başka herhangi bir şeyden geçiş yapabilirsiniz. Ayrıca, bir bileşendeki jQuery işlevselliği ve global jQuery mutlaka aynı şey değildir.
Ancak sorun şu ki, bileşenin Alien uygulamanız bağlamında gayet iyi çalıştığını onaylasanız bile, onu Shadow DOM'a yerleştirdiğinizde jQuery eklentileriniz ve jQuery'ye dayanan diğer kodlar çalışmayacaktır.
Gölge DOM'de jQuery
Rastgele bir jQuery eklentisinin genel bir başlatmasına bir göz atalım:
$('.my-selector').fancyPlugin();
Bu şekilde, .my-selector
sahip tüm öğeler fancyPlugin
tarafından işlenecektir. Bu başlatma biçimi, .my-selector
öğesinin global DOM'de bulunduğunu varsayar. Ancak, böyle bir öğe Shadow DOM'a eklendiğinde, tıpkı stillerde olduğu gibi, gölge sınırları jQuery'nin ona gizlice girmesini engeller. Sonuç olarak, jQuery, Shadow DOM içindeki öğeleri bulamıyor.
Çözüm, seçiciye, jQuery'nin arama yapacağı kök öğeyi tanımlayan isteğe bağlı ikinci bir parametre sağlamaktır. Ve burası, shadowRoot
tedarik edebileceğimiz yer.
$('.my-selector', this.shadowRoot).fancyPlugin();
Bu şekilde, jQuery seçicileri ve bunun sonucunda eklentiler gayet iyi çalışacaktır.
Alien bileşenlerinin hem gölge DOM olmadan Alien'de hem de Shadow DOM içindeki Host'ta kullanılmak üzere tasarlandığını unutmayın. Bu nedenle, varsayılan olarak Shadow DOM'un varlığını varsaymayan daha birleşik bir çözüme ihtiyacımız var.
React uygulamamızda MainSection
bileşenini analiz ettiğimizde, bunun documentRoot
özelliğini ayarladığını görüyoruz.
... this.documentRoot = this.props.root? this.props.root: document; ...
Bu nedenle, geçirilen root
özelliğini kontrol ederiz ve eğer varsa, bunu documentRoot
olarak kullanırız. Aksi takdirde, document
geri döneriz.
Bu özelliği kullanan araç ipucu eklentisinin başlatılması:
$('[data-toggle="tooltip"]', this.documentRoot).tooltip({ container: this.props.root || 'body' });
Bir bonus olarak, bu durumda araç ipucunu enjekte etmek için bir kap tanımlamak için aynı root
özelliğini kullanıyoruz.
Şimdi, Alien bileşeni root
özelliğini kabul etmeye hazır olduğunda, bileşenin karşılık gelen Frankenstein sarmalayıcısında oluşturmasını güncelleriz:
// `appWrapper` is the root element within wrapper's Shadow DOM. ReactDOM.render(<MainApp root={ appWrapper } />, appWrapper);
Ve bu kadar! Bileşen, genel DOM'da olduğu gibi Shadow DOM'da da iyi çalışır.
Çoklu sarmalayıcı senaryosu için Web paketi yapılandırması
Heyecan verici kısım, birkaç sarmalayıcı kullanırken Webpack'in yapılandırmasında oluyor. Vue bileşenlerindeki CSS Modülleri veya React'teki stilli bileşenler gibi paketlenmiş stiller için hiçbir şey değişmez. Ancak, küresel stiller şimdi biraz bükülmeli.
Unutmayın, style-loader
(genel stil sayfalarını doğru Shadow DOM'a enjekte etmekten sorumlu) esnek olmadığını çünkü insert
seçeneği için her seferinde yalnızca bir seçiciye ihtiyaç duyduğunu söylemiştik. Bu, Webpack dışında bir paketleyici üzerindeyseniz, oneOf
kuralı veya benzerini kullanarak her sarmalayıcı için bir alt kurala sahip olmak için Web paketindeki .css
kuralını bölmemiz gerektiği anlamına gelir.
Bir örnek kullanarak açıklamak her zaman daha kolaydır, bu yüzden bu sefer geçişten Vue'ye geçiş hakkında konuşalım (ancak React'e geçişte olan hemen hemen aynıdır):
... oneOf: [ { issuer: /Header/, use: [ { loader: 'style-loader', options: { insert: 'frankenstein-header-wrapper' } }, ... ] }, { issuer: /Listing/, use: [ { loader: 'style-loader', options: { insert: 'frankenstein-listing-wrapper' } }, ... ] }, ] ...
Yapılandırması her durumda aynı olduğu için css-loader
hariç tuttum. Bunun yerine style-loader
hakkında konuşalım. Bu yapılandırmada, stil sayfasını isteyen dosyanın adına bağlı olarak (Web paketinde issuer
kuralı) *-header-*
veya *-listing-*
içine <style>
etiketi ekleriz. Ancak, bir Alien bileşenini oluşturmak için gereken global stil sayfasının iki yerde içe aktarılabileceğini unutmamalıyız:
- Alien bileşeninin kendisi,
- Bir Frankenstein paketi.
Ve burada, bir Alien bileşeninin adı ve karşılık gelen bir sarmalayıcı eşleştiğinde, yukarıda açıklanan sarmalayıcılar için adlandırma kuralını takdir etmeliyiz. Örneğin, Header.vue
adlı bir Vue bileşeninde içe aktarılan bir stil sayfamız varsa, *-header-*
sarmalayıcısını düzeltir. Aynı zamanda, bunun yerine, sarmalayıcıdaki stil sayfasını içe aktarırsak, bu tür stil sayfası, yapılandırmada herhangi bir değişiklik olmadan sarmalayıcı Header-wrapper.js
olarak adlandırılırsa tam olarak aynı kuralı izler. Listing.vue
bileşeni ve buna karşılık gelen sarmalayıcı Listing-wrapper.js
için aynı şey. Bu adlandırma kuralını kullanarak, paketleyicimizdeki yapılandırmayı azaltıyoruz.
Tüm bileşenleriniz taşındıktan sonra, geçişin son adımına sıra gelir.
7. Uzaylıya Geç
Bir noktada, geçişin ilk adımında tanımladığınız bileşenlerin tamamının Frankenstein sarmalayıcıları ile değiştirildiğini öğrenirsiniz. Gerçekten hiçbir jQuery uygulaması kalmaz ve sahip olduğunuz şey, esasen, Host aracılığıyla birbirine yapıştırılmış Alien uygulamasıdır.
Örneğin, jQuery uygulamasındaki index.html
içerik kısmı - her iki mikro hizmetin taşınmasından sonra - şu anda şuna benzer:
<section class="todoapp"> <frankenstein-header-wrapper></frankenstein-header-wrapper> <frankenstein-listing-wrapper></frankenstein-listing-wrapper> </section>
Şu anda jQuery uygulamamızı etrafta tutmanın bir anlamı yok: bunun yerine Vue uygulamasına geçmeli ve tüm sarmalayıcılarımızı, Shadow DOM ve süslü Webpack yapılandırmalarımızı unutmalıyız. Bunu yapmak için zarif bir çözümümüz var.
HTTP istekleri hakkında konuşalım. Burada Apache konfigürasyonundan bahsedeceğim, ancak bu sadece bir uygulama detayı: anahtarı Nginx'te veya başka bir şeyde yapmak Apache'deki kadar önemsiz olmalıdır.
Sunucunuzdaki /var/www/html
klasöründen sitenize hizmet verdiğinizi düşünün. Bu durumda, httpd.conf
veya httpd-vhost.conf
dosyanız, aşağıdaki gibi o klasöre işaret eden bir girdiye sahip olmalıdır:
DocumentRoot "/var/www/html"
Frankenstein'ın jQuery'den React'e geçişinden sonra uygulamanızı değiştirmek için yapmanız gereken tek şey DocumentRoot
girişini aşağıdaki gibi güncellemek:
DocumentRoot "/var/www/html/react/build"
Alien uygulamanızı oluşturun, sunucunuzu yeniden başlatın ve uygulamanız doğrudan Alien klasöründen sunulur: React uygulaması react/
klasöründen sunulur. Bununla birlikte, aynısı, elbette, Vue veya taşıdığınız diğer herhangi bir çerçeve için de geçerlidir. Bu nedenle Host ve Alien'ı herhangi bir zamanda tamamen bağımsız ve işlevsel tutmak çok önemlidir çünkü bu adımda Alien'ınız Hostunuz olur.
Artık tüm Shadow DOM, Frankenstein sarmalayıcıları ve göçle ilgili diğer eserler de dahil olmak üzere Alien klasörünüzün etrafındaki her şeyi güvenle kaldırabilirsiniz. Zaman zaman zorlu bir yoldu, ancak sitenizi taşıdınız. Tebrikler!
Çözüm
Bu yazıda kesinlikle biraz engebeli araziden geçtik. Ancak bir jQuery uygulamasıyla başladıktan sonra onu hem Vue'ya hem de React'e taşımayı başardık. Yol boyunca bazı beklenmedik ve önemsiz sorunlar keşfettik: stili düzeltmemiz, JavaScript işlevselliğini düzeltmemiz, bazı paketleyici yapılandırmaları sunmamız ve çok daha fazlası. Ancak, gerçek projelerde neler bekleyebileceğimiz konusunda bize daha iyi bir genel bakış sağladı. Sonunda, geçiş devam ederken nihai sonuç hakkında şüpheci olmak için tüm haklara sahip olmamıza rağmen, jQuery uygulamasından kalan bit olmadan çağdaş bir uygulamamız oldu.

Frankenstein Göçü ne gümüş bir kurşun ne de korkutucu bir süreç olmalıdır. Projeleri öngörülebilir bir şekilde yeni ve sağlam bir şeye dönüştürmeye yardımcı olan, birçok projeye uygulanabilen yalnızca tanımlanmış algoritmadır.