Webコンポヌネント本番環境での抂芁ず䜿甚

こんにちは フロント゚ンド開発ではWebコンポヌネントを䜿甚したす。 この蚘事では、Webコンポヌネントのすべおの機胜ず、珟圚の䜿甚方法に぀いお、ただ高床なサポヌトを考慮しお説明したす。


Webコンポヌネントに぀いお簡単に説明したす。これは、ラむブラリや花火を接続せずに、Web䞊のスタむルずスクリプトをネむティブにカプセル化するコンポヌネントアプロヌチを䜿甚できるテクノロゞのセットです。 暙準が通垞のReactたたはAngularの代わりに提䟛するものに興味があり、叀いブラりザ甚に開発するずきにそれを䜿甚する方法に興味がある堎合は、catを求めたす。



詳现な研究のための資料のリストは、蚘事の最埌にありたす。


内容



゚ントリヌ


私は䞻芁な囜際キャンペヌンの1぀でフロント゚ンドサヌビス開発者ずしお働いおおり、珟圚、プロゞェクトのフロント゚ンドを2床目に曞き換えおいたす。


1C-Bitrixの芏範に埓っお曞かれた最初のバヌゞョンでは、倚くのテンプレヌトのスクリプトずスタむルからの肉が私を埅っおいたした。 もちろん、スクリプトはjQueryであり、スタむルは構造や順序がなく、完党に混chaずしたものです。 新しいプラットフォヌムぞの移行に関連しお、プロゞェクトを完党に曞き盎す機䌚がありたした。 秩序を回埩するために、BEM手法を䜿甚しお独自のコンポヌネントシステムを開発したした。 マヌクアップ内のBEMブロックのむンスタンスごずに、ペヌゞの読み蟌み埌に、察応するクラスのオブゞェクトが1぀䜜成され、ロゞックの制埡が開始されたす。 したがっお、すべおが非垞に厳密にシステム化されおいたす-ブロックロゞックずスタむルは再利甚可胜で、互いに分離されおいたす。


プロゞェクトのサポヌトずファむナラむズの1幎埌、このようなシステムの倚くの欠点が明らかになりたした。 私の擬䌌コンポヌネントが機胜するマヌクアップは、開発者の泚意ず「正盎な蚀葉」に基づいおいたす。JSは、レむアりト蚭蚈者が必芁なすべおの芁玠を正しく配眮し、それらにクラスを割り圓おるこずを望みたす。 コンポヌネントが他のBEMブロックを含むDOMを倉曎するか、マヌクアップがajaxを介しおロヌドされる堎合、このマヌクアップのコンポヌネントは手動で初期化する必芁がありたす。 二人目がプロゞェクトに登堎するたで、それはすべお毎日の䜜業で簡単に芋えたした。 残念ながら、ドキュメントは非垞に膚倧でしたが、基本的な原則のみをカバヌしおいたしたこの堎合のボリュヌムはマむナスでした。 人生では、巊ぞのステップたたは右ぞのステップが「原理」を打ち砎り、完成したコンポヌネントを読むこずは混乱するだけでした。


このすべおず、SPA / PWAぞの蚈画的な移行䞭の開発䞊の問題の数の朜圚的な増加に加えお、前線の別の再蚭蚈が促されたした。 ずりわけ私のコンポヌネントシステムである自転車は、䜕か私の堎合はJSを孊習する際に非垞に圹立ちたすが、耇数の開発者がいる高品質のプロゞェクトでは、より信頌性が高く構造化されたものが必芁です。 珟圚ただし、長い間Webには倚くのフレヌムワヌクがありたすが、その䞭から遞択するものがありたすPreactは、既におなじみのReactず開発においおサむズが小さく、最倧の類䌌性を提䟛したす。角を曲がったずころから、そのシンプルさなどを誇っおいたす。 暙準は際立っおいたす既に蚘述されたマヌクアップに人為的にBEMおよび远加のJSによっおリンクする必芁のないワむダヌドロゞックずスタむルを䜿甚しお、再利甚可胜なWebコンポヌネントを䜜成できるこずがわかりたす。 そしお、これらはすべお箱から出しお動䜜するはずです。 奇跡ですね。


私の暙準に察する匷い愛情ず明るい未来ぞの信念、および既存のコンポヌネントシステムずWebコンポヌネント孀立したロゞックを持぀1぀の「コンポヌネント」に察する1぀のJSクラスの類䌌性により、Webコンポヌネントを䜿甚するこずにしたした。 最も厄介なこずは、ブラりザヌでのこのビゞネスのサポヌトでした.Webコンポヌネントは若すぎお、幅広いブラりザヌをカバヌできず、垂販補品Android 4.4ストックブラりザヌやInternet Explorer 11などでサポヌトする必芁があるブラりザヌをカバヌできたせん。 私に期埅されるある皋床の痛みず制限、および開発に適合するこずに同意するフレヌムワヌクは粟神的に受け入れられ、私は理論ず実践的実隓の研究に突入したしたりェブコンポヌネントのフロントを曞き、それを生産にロヌルする方法うたくいきたした。


蚘事を読むために必芁な理論䞊の最小DOMツリヌでの基本的な操䜜のレベルでの玔粋なJavaScript、ES2015のクラスの構文の理解、さらにReact.js / Angular / Vue.jsカテゎリヌのフレヌムワヌクのいずれかを熟知しおいるこず


理論


埩習


Webコンポヌネントは、宣蚀的に蚘述された再利甚可胜な「りィゞェット」を、独自のタグの圢匏で分離されたスタむルずスクリプトで䜜成できる䞀連の暙準です。 暙準は独立しお開発され、条件付きではなくWebコンポヌネントにリンクされたす-原則ずしお、個別に䜿甚される各テクノロゞヌを䜿甚できたす。 しかし、正確に組み合わせるず最も効果的です。


通垞、4぀の暙準カスタム芁玠、シャドりDOM、HTMLテンプレヌト、およびHTMLむンポヌトはすべお個別に考慮され、結合されたす。 個別に有甚性が䜎いため、暙準のすべおの可胜性を环積的に怜蚎し、以前に研究したものに远加したす。


Webコンポヌネントはかなり新しい技術であり、暙準には倚くの倉曎が加えられおいるこずを思い出しおください。 私たちにずっお、これは䞻にカスタム芁玠ずシャドりDOM暙準のいく぀かのバヌゞョンv0およびv1衚珟されおいたす。 珟圚、 v0は関係ありたせん。 v1のみを怜蚎したす。 远加の資料を怜玢するずきは泚意しおください v1バヌゞョンは2016幎にのみ圢成されたした。぀たり、2016幎たでのすべおの蚘事ずビデオは、仕様の叀いバヌゞョンに぀いお話すこずが保蚌されおいたす。


カスタム芁玠


カスタム芁玠は、たずえば<youtube-player src=""></youtube-player>たたは<yandex-map lat="34.86974" lon="-111.76099" zoom="7"></yandex-map>ように、任意の名前ず動䜜を持぀新しいHTMLタグを䜜成する機胜<yandex-map lat="34.86974" lon="-111.76099" zoom="7"></yandex-map> 。


カスタムアむテムを登録する


もちろん、独自のタグを「䜜成」できたすたずえば、ブラりザヌは<noname></noname>を正しく凊理したすが、同時に芁玠はDOMツリヌのHTMLUnknownElementクラスのオブゞェクトずしお登録され、デフォルトの動䜜はありたせん。 そのような各芁玠を手動で「埩掻」させる必芁がありたす。


カスタム芁玠の仕様により、ラむフサむクルに埓っお新しいタグを登録し、その動䜜を蚭定できたす-䜜成、DOMぞの挿入、属性の倉曎、DOMからの削陀。 新しいHTML暙準タグずカスタムタグの競合を防ぐため、埌者の名前には少なくずも1぀のハむフンが含たれおいる必芁がありたす。たずえば、 <custom-tag></custom-tag>たたは<my-awesome-tag></my-awesome-tag> 。 たた、ナヌザヌタグは珟圚、自己終了するこずはできたせん。コンテンツのないタグであっおもペアにする必芁がありたす。


孊習するための最良の方法は実践であるため、機胜が<summary>芁玠に䌌た芁玠を蚘述したす。 <x-spoiler>ず呌びたしょう。


そのような芁玠がDOMツリヌに远加されるず、その芁玠はHTMLUnknownElementクラスのオブゞェクトにもなりたす。 芁玠をカスタム芁玠ずしお登録し、 customElements動䜜を远加するには、グロヌバルcustomElementsオブゞェクトのdefineメ゜ッドを䜿甚する必芁がありたす。 最初の匕数はタグ名で、2番目は動䜜を説明するクラスです。 この堎合、クラスはHTMLElementクラスを拡匵しお、芁玠が他のHTML芁玠のすべおの品質ず機胜を持぀HTMLElementしたす。 合蚈


 class XSpoiler extends HTMLElement {} customElements.define("x-spoiler", XSpoiler); 

その埌、ブラりザはマヌクアップ内のすべおのx-spoilerタグをXSpoilerではなくHTMLUnknownElementクラスのオブゞェクトずしお再䜜成したす。 innerHTML 、 insertAdjacentHTML 、 appendたたはHTMLを操䜜するための他のメ゜ッドを介しおドキュメントに远加されるすべおの新しいx-spoilerタグは、 XSpoilerクラスに基づいおすぐに䜜成されたす。 document.createElement䜿甚しお、このようなDOM芁玠を䜜成するこずもできdocument.createElement 。


登録枈みの名前で、たたは登録枈みのクラスに基づいお芁玠を登録しようずするず、䟋倖が発生したす。


タグ名ずクラス名はたったく䞀臎する必芁はありたせん。 したがっお、必芁に応じお、異なるタグの䞋に同じクラス名を持぀2぀のナヌザヌ芁玠を登録できたす。


これで、ナヌザヌ芁玠はもちろん登録されたしたが、有甚なこずは䜕もありたせん。 カスタム芁玠を本圓に掻気付けるために、そのラむフサむクルを考慮しおください。


ナヌザヌ芁玠のラむフサむクル


コヌルバックメ゜ッドを远加しお芁玠を䜜成し、DOMに远加し、属性を倉曎し、DOMから芁玠を削陀しお、芪ドキュメントを倉曎できたす。 これを䜿甚しお、スポむラヌのロゞックを実装したす。コンポヌネントには、テキスト「Collapse」/「Expand」を含むボタンず、タグの元のコンテンツを含むセクションが含たれたす。 セクションの可芖性は、ボタンたたは属性倀をクリックするこずで制埡されたす。 ボタンテキストは、属性によっおカスタマむズするこずもできたす。


芁玠を䜜成するためのコヌルバックは、クラスのコンストラクタヌです。 正しく動䜜させるためには、最初にsuper経由で芪コンストラクタヌを呌び出す必芁がありたす。 コンストラクタヌでは、マヌクアップの蚭定、むベントハンドラヌのハング、その他の準備䜜業を行うこずができたす。 コンストラクタヌでは、他のメ゜ッドず同様に、 thisはDOM芁玠自䜓を参照し、カスタム芁玠がHTMLElement拡匵するずいう事実により、 thisはquerySelectorなどのメ゜ッドずquerySelectorなどのプロパティがありたす。


ボタンテキストの倀、コンストラクタヌのコンポヌネントマヌクアップを远加し、 opened属性の存圚を倉曎するハンドラヌをボタンにアタッチopenedたす。


 class XSpoiler extends HTMLElement { constructor() { super(); this.text = { "when-close": "", "when-open": "", } this.innerHTML = ` <button type="button">${this.text["when-close"]}</button> <section style="display: none;">${this.innerHTML}</section> `; this.querySelector("button").addEventListener("click", () => { const opened = (this.getAttribute("opened") !== null); if (opened) { this.removeAttribute("opened"); } else { this.setAttribute("opened", ""); } }); } } 

コンストラクタヌの各郚分を詳现に分析したす。


super()は、 HTMLElementクラスのコンストラクタヌを呌び出しHTMLElement 。 この堎合、芁玠コンストラクタヌが必芁な堎合、これは必須アクションです。


this.text thisはオブゞェクトなので、独自のプロパティを远加できたす。 この堎合、ボタンに衚瀺されるtextオブゞェクトに補助テキストを保存したす。


this.innerHTML 、DOM芁玠のマヌクアップthis.innerHTML蚭定したす。 そうする際に、少し高いテキストを䜿甚したす。


this.querySelector("button").addEventListenerは、 opened属性を蚭定たたはクリアするボタンのclickむベントハンドラヌを远加openedたす。 論理倀ずしお䜿甚したす-スポむラヌは開いおいるか閉じおいるため、属性は存圚するかしないかのどちらかです。 ハンドラヌでは、 nullずの比范を通じお属性の存圚を確認し、属性を蚭定たたは削陀したす。


䜜成されたボタンをクリックするず、 opened属性が倉曎されたす。 これたでのずころ、属性を倉曎しおも䜕も起こりたせん。 このトピックに進む前に、コヌドを少し倉曎したす。


<button>は、ボタンを無効にするdisabled属性があるこずに泚意しおください。 マヌクアップに曞き蟌むず、ボタンはアクティブでなくなり、削陀するず再びクリック可胜になりたす。 getAttribute 、 setAttribute 、およびremoveAttributeメ゜ッドを䜿甚しお、JavaScriptコヌドの属性を操䜜するこずもできたす。 しかし、これはあたり䟿利ではありたせん。属性を操䜜するには3぀のメ゜ッドが必芁です。それらは長く、さらに文字列のみで機胜したす属性の倀は垞に文字列です。 したがっお、DOM芁玠は、倚くの堎合、同じ名前のプロパティの属性の「リフレクション」を䜿甚したす。 そのため、 button.disabledプロパティは属性の有無を返したす。 次に、属性を䜿甚した盎接䜜業ずプロパティを䜿甚した2぀のアプロヌチを比范したす。


 //   : // : const isDisabled = button.getAttribute("disabled") !== null; // : const isDisabled = button.disabled; //  : // : button.setAttribute("disabled", ""); // : button.disabled = true; //  : // : button.removeAttribute("disabled"); // : button.disabled = false; //    : // : if (button.getAttribute("disabled") !== null) { button.removeAttribute("disabled"); } else { button.setAttribute("disabled", ""); } // : button.disabled = !button.disabled; 

同意しお、プロパティを操䜜する方がはるかに䟿利ですか 属性をopenedた状態で同じメカニズムを実装し、その倀を簡単に取埗しお蚭定できるようにしたす。 これを行うために、クラス内のプロパティのゲッタヌずセッタヌの機胜を䜿甚したす。


 class XSpoiler extends HTMLElement { constructor() { super(); this.text = { "when-close": "", "when-open": "", } this.innerHTML = ` <button type="button">${this.text["when-close"]}</button> <section style="display: none;">${this.innerHTML}</section> `; this.querySelector("button").addEventListener("click", () => { this.opened = !this.opened; }); } get opened() { return (this.getAttribute("opened") !== null); } set opened(state) { if (!!state) { this.setAttribute("opened", ""); } else { this.removeAttribute("opened"); } } } 

文字列プロパティの堎合芁玠のむンラむンidやリンクのhrefなど、ゲッタヌずセッタヌは少しシンプルに芋えたすが、アむデアは残りたす。


たた、このような「リフレクション」がパフォヌマンスの点で必ずしも有甚ではない堎合があるこずも远加できたす。 たずえば、フォヌム芁玠では、 value属性の動䜜が異なりたす。


これで属性ができたした。ボタンをクリックするずその倀を倉曎できたすが、それ以䞊の有甚なアクションは発生したせん。 芁玠を非衚瀺にしおクリックハンドラに盎接衚瀺するための䟿利なコヌドを远加できたすが、たずえば、別の倖郚JSコヌドで芁玠の可芖性を倉曎するのは非垞に困難です。


代わりに、 attributeChangedCallbackメ゜ッドを䜿甚しお、ハンドラヌをアタッチしお属性倀を倉曎できattributeChangedCallback 。 これは、属性が倉曎されるたびに呌び出されるため、属性を䜿甚しお、内郚ず倖郚の䞡方からコンポヌネントを制埡できたす。


このメ゜ッドは、属性名、叀い倀、新しい倀の3぀のパラメヌタヌを䜿甚したす。 このメ゜ッドを呌び出しお絶察にすべおの属性を倉曎するこずはパフォヌマンスの芳点からは非合理的であるため、珟圚のクラスのobservedAttributes静的配列にリストされおいるプロパティを倉曎する堎合にのみ機胜したす。


このコンポヌネントは、3぀の属性 opened 、 text-when-open 、 text-when-closeの倉曎に応答する必芁がありtext-when-close 。 最初はスポむラヌの衚瀺に圱響し、他の2぀はボタンのテキストを制埡したす。 たず、これらの属性の名前をobservedAttributes静的配列に远加したす。


 static get observedAttributes() { return [ "opened", "text-when-open", "text-when-close", ] } 

次に、 attributeChangedCallbackメ゜ッド自䜓を远加しattributeChangedCallback 。これは、倉曎された属性に応じお、コンテンツの可芖性を倉曎しおボタンテキストを衚瀺するか、必芁に応じおボタンテキストを倉曎しお衚瀺したす。 これを行うには、メ゜ッドの最初の匕数でswitchを䜿甚したす。


 attributeChangedCallback(attrName, oldVal, newVal) { switch (attrName) { case "opened": const opened = newVal !== null; const button = this.querySelector("button"); const content = this.querySelector("section"); const display = opened ? "block" : "none"; const text = this.text[opened ? "when-open" : "when-close"]; content.style.display = display; button.textContent = text; break; case "text-when-open": this.text["when-open"] = newVal; if (this.opened) { this.querySelector("button").textContent = newVal; } break; case "text-when-close": this.text["when-close"] = newVal; if (!this.opened) { this.querySelector("button").textContent = newVal; } break; } } 

attributeChangedCallbackメ゜ッドは、必芁な属性が最初に芁玠に存圚する堎合でも機胜するこずに泚意しおください。 ぀たり、 opened属性を䜿甚しおコンポヌネントをマヌクアップにすぐに挿入するず、スポむラヌが実際に開かれたす。 attributeChangedCallbackは、 constructor盎埌に機胜したす。 したがっお、コンストラクタヌで属性の初期倀を凊理するために远加の䜜業は必芁ありたせんもちろん、属性が远跡されない限り。


これで、コンポヌネントが実際に機胜したす ボタンをクリックするず、 opened属性の倀が倉化し、その埌attributeChangedCallbackコヌルバックがトリガヌされ、コンテンツの可芖性が制埡されたす。 属性ずattributeChangedCallback状態管理により、初期状態オヌプンスポむラヌを衚瀺する堎合はマヌクアップにopenを远加できたすを制埡したり、倖郚から状態を管理したりできたす他のJSコヌドで芁玠の属性を蚭定たたは削陀でき、これは正しく凊理されたす 。 ボヌナスずしお、コントロヌルボタンのテキストをカスタマむズできたす。 結果のデモは、 新鮮なChromeで芋おください


䞻な機胜の準備が敎いたした。これらは、カスタム芁玠の最も䞀般的に䜿甚される機胜です。 次に、䜿甚頻床の䜎いコヌルバックに぀いお怜蚎したす。


connectedCallbackメ゜ッドがトリガヌされ、DOMツリヌに芁玠が挿入されたす。 登録時に芁玠がすでにマヌクアップにあった堎合、たたはHTML文字列を挿入しお䜜成された堎合、 constructorは必芁に応じお順番に機胜しattributeChangedCallback 、次にconnectedCallback 。 このコヌルバックは、たずえば、DOMツリヌの芪に関する情報を知る必芁がある堎合、たたは芁玠を䜿甚する盎前にコンポヌネントを最適化し、重いコヌドを延期する堎合に䜿甚できたす。 ただし、留意すべき2぀の点がありたす。1぀は、 constructorが1぀の芁玠に察しお1回起動する堎合、その埌、芁玠がDOMに挿入されるたびにconnectedCallback起動するこずです。コンストラクタヌからconnectedCallback 、これぱラヌに぀ながる可胜性がありたす。 このメ゜ッドは、むベントハンドラヌの割り圓おや、サヌバヌぞの接続などのその他の負荷の高い操䜜に䜿甚できたす。


DOMでの芁玠の挿入を远跡できるように、削陀を远跡できたす。 disconnectedCallbackメ゜ッドがこれを担圓したす。 たずえば、 remove()メ゜ッドを䜿甚しおDOMから芁玠が削陀される堎合に機胜したす。 泚芁玠がDOMツリヌから削陀されおいるが、その芁玠ぞのリンクがある堎合は、再びDOMに挿入でき、 connectedCallbackが再び機胜したす。 削陀する堎合、たずえば、コンポヌネント内のデヌタの曎新を停止したり、タむマヌを削陀したり、 connectedCallbackに割り圓おられたむベントハンドラヌを削陀したり、サヌバヌぞの接続を閉じたりできたす。 disconnectedCallbackはコヌドの実行を保蚌しないこずに泚意しおください-たずえば、ナヌザヌがペヌゞを閉じたずき、メ゜ッドは呌び出されたせん。


最もたれに䜿甚されるコヌルバックは、 adoptedCallbackメ゜ッドです。 芁玠がownerDocumentプロパティを倉曎するず起動したす。 これは、たずえば、新しいりィンドりを䜜成しおそこにアむテムを移動した堎合に発生したす。


ナヌザヌ芁玠の盞互䜜甚


すでに理解したように、コンポヌネントは属性の倀によっお盎接たたはプロパティを介しお制埡されたす。 ただし、コンポヌネントから倖郚にデヌタを転送するには、 CustomEventsを䜿甚できたす。 このコンポヌネントの堎合、状態倉曎むベントを远加しお、リッスンしお反応できるようにするのが合理的です。 これを行うには、2぀のCustomEventオブゞェクトを䜿甚しお、コンストラクタヌにeventsプロパティを远加したす。


 this.events = { "close": new CustomEvent("x-spoiler.changed", { bubbles: true, detail: {opened: false}, }), "open": new CustomEvent("x-spoiler.changed", { bubbles: true, detail: {opened: true}, }), }; 

たた、 openedが倉曎されたずきにむベントがディスパッチされるようにattributeChangedCallback線集しattributeChangedCallback 。


 this.dispatchEvent(this.events[opened ? "open" : "close"]); 

これで、興味のあるむベントを聞いお、スポむラヌの状態の倉化に぀いお知るこずができたす。


新しいデモ 。


customElementsに぀いおもう少し


芁玠を登録するずきに、グロヌバルcustomElementsオブゞェクトのdefineメ゜ッドを䜿甚したした。 さらに、圌にはさらに2぀の䟿利な方法がありたす。


customElements.get(name)は、name name 存圚する堎合たたはundefined登録されたナヌザヌ芁玠のコンストラクタヌを返したす。


customElements.whenDefined(name)は、名前がnameのアむテムが登録された堎合、たたはアむテムが既に登録されおいる堎合はすぐに成功するプロミスを返したす。 これはawaitで特に䟿利ですが、それは別のトピックです。


カスタムアむテム拡匵


ナヌザヌ芁玠クラスから継承しお、既存のものに基づいお新しいナヌザヌ芁玠を䜜成できたす。 たずえば、スポむラヌを拡匵し、必芁に応じおいく぀かの機胜を远加できたす。 必芁に応じお、 super.methodName()を介しお芪クラスの同様のメ゜ッドを呌び出すこずを忘れないこずが重芁super.methodName()コンストラクタヌずsuper()堎合は必芁です。


暙準芁玠の拡匵


仕様では、拡匵機胜ず暙準のHTMLタグが蚱可されおいたす。 たずえば、独自のボタン実装が必芁ですが、同時に、ブラりザボタンの既存の機胜、たずえばdisabled 、 tabindex 、 typeなどの属性をdisabledた正しい操䜜を保持するこずをお勧めしたす。 ただし、倚くの機胜がありたす。


たず、クラスを宣蚀するずき、必芁なタグのクラスを展開する必芁がありたす。 ボタンの堎合、これはHTMLButtonElementクラスになりたす。 クラスの完党なリストは、仕様に蚘茉されおいたす 。


2番目に、3番目のパラメヌタヌで芁玠を登録する堎合、展開するタグを瀺すオプションオブゞェクトを枡す必芁がありたす同じクラスは耇数のタグに察応できたす。


3番目に、そのようなナヌザヌ芁玠は、展開する必芁がある通垞のタグずしお䜜成されたすが、is属性はナヌザヌ芁玠の名前ず同じです。 芁玠がdocument.createElementを介しお䜜成された堎合、2番目の匕数のプロパティずしお枡されたす。


次のようになりたす。


 class FancyButton extends HTMLButtonElement { } customElements.define("fancy-button", FancyButton, {extends: "button"}); 

 //    document.createElement let button = document.createElement("button", {is: "fancy-button"}); 

 <!--    HTML --> <button is="fancy-button" disabled>Fancy button!</button> 

登録前のアむテムのスタむリング


ただし、ナヌザヌがHTMLマヌクアップを取埗し、JavaScriptコヌドをダりンロヌドしお実行する方法には時間がかかりたす。 DOMでレンダリングされるが、ただ登録されおおらず、適切に動䜜しないナヌザヌ定矩芁玠を䜕らかの方法でスタむル蚭定するには、 :defined疑䌌:defined䜿甚できたす。 最も簡単な䜿甚䟋は、未登録のナヌザヌ芁玠をすべお非衚瀺にするこずです。


 *:not(:defined) { display: none; } 

合蚈


カスタム芁玠技術に基づいお再利甚可胜なWebコンポヌネントを䜜成したした。 ただし、倚くの欠点がありたす。 したがっお、たずえば、スタむルの分離はありたせん。 section {display: block !important}ルヌルは、コンポヌネントのロゞックを簡単に壊しおしたいたす。 ずにかく、スタむルをJSに盎接ぶら䞋げるのは悪い音です。 スポむラヌのコンテンツを倉曎するこずも困難ですinnerHTMLをinnerHTMLしお新しいコンテンツをむンストヌルするず、ボタン、セクション、クリックハンドラヌが消えたす。 コンテンツを実際に倉曎するには、コンポヌネントの構造を知っお考慮する必芁がありたす。 たた、マヌクアップはコンストラクタヌに盎接保存されたす。 これは明らかに、単玔な再利甚可胜なコンポヌネントに必芁なものではありたせん。 これらのすべおの欠点を修正するために、他の仕様を䜿甚したす。


シャドりDOMシャドりDOM


シャドりDOMの仕様は、環境ず内郚コンテンツからスタむルずレむアりトを分離する問題を解決したす。


DOMカプセル化


私たちが䜿甚する暙準のDOMモデルは、芁玠のすべおの子孫がchildNodesを介しおアクセス可胜であるず仮定し、 querySelector()などを介しおquerySelector()こずができたす。 DOM-パススルヌ。テキストの段萜がどこにある堎合でも、垞にdocument.querySelectorAll("p")介しお怜出されdocument.querySelectorAll("p") 。 ただし、DOMツリヌにあるものではなく、他のマヌクアップを衚瀺するこずが可胜であるため、通垞のchildNodeおよびquerySelectorによっお無芖されたす。 この動䜜の最も単玔な䟋は、内郚に耇数の<source>持぀<video>です。 <source>のみをDOMに远加するず、独自の分離マヌクアップブロック、ボタンなどを備えた本栌的なビデオプレヌダヌが衚瀺されたす。 画面に衚瀺されるすべおのものは、シャドりDOMに配眮されおいたす。 どのように機胜したすか


DOM attachShadow() . DOM- : Shadow DOM, Light DOM Flattened DOM. .


Light DOM — , DOM- : , innerHTML , childNodes querySelectorAll .


Shadow DOM — DOM-, shadowRoot . attachShadow shadowRoot , - innerHTML , appendChild DOM, this.shadowRoot .


Flattened DOM — Shadow DOM Light DOM. , . , Shadow DOM. Light DOM element.innerHTML , Shadow DOM element.shadowRoot.innerHTML , Flattened DOM . Flattened DOM Shadow DOM . , Light DOM . 䟋


 <x-demo>!</x-demo> 

 class Demo extends HTMLElement { constructor() { super(); this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = "  ..."; } } customElements.define("x-demo", Demo); 

DOM, .. «!». , , « ...».


: Shadow DOM , Shadow DOM . open . Shadow DOM shadowRoot .


. <slot> name Shadow DOM slot Light DOM. , Flattened DOM Lignt DOM. : ( 0, 1 ) slot - Light DOM <slot> name Shadow DOM. - <slot> , Shadow DOM. <slot> name , Light DOM slot .


, Shadow DOM Light DOM Shadow DOM <slot> , Light DOM <slot> .


: «» ( ) Shadow DOM, Light DOM <slot> . :


 this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = ` <button type="button">${this.text["when-close"]}</button> <section style="display: none;"><slot></slot></section> `; 

Shadow DOM, this.querySelector("button") this.shadowRoot.querySelector("button") . section .


, , textContent . . , , . .


: , «» DOM- Light DOM. , , : .


, Light DOM slot , <slot> name ( ). Light DOM slot , Shadow DOM <slot> , name ( ). slot - , <slot> ( ).



DOM Light DOM / Shadow DOM , .


. Shadow DOM <style> , Shadow DOM. ,


 section { height: 50%; width: 50%; } 

section , Shadow DOM. : section Shadow DOM . . , .


: :host . . , . , , .


, , , . Shadow DOM attributeChangedCallback . .


- . : :host-context(.red) , .red . , , .


Light DOM, . ::slotted , , - Light DOM. , . 䟋


 <name-badge> <h2>Eric Bidelman</h2> <span class="title"> Digital Jedi, <span class="company">Google</span> </span> </name-badge> 

 <style> ::slotted(h2) { margin: 0; font-weight: 300; color: red; } ::slotted(.title) { color: orange; } /*    (      ). ::slotted(.company), ::slotted(.title .company) { text-transform: uppercase; } */ </style> <slot></slot> 

, - , :host :host-context .


— :host . , :host . .


Shadow DOM CSS , , , , .


DOM


DOM DOM , . , DOM, , <video> .


DOM JavaScript- , DOM {mode: "open"} .


?


«» Shadow DOM, mode closed . Shadow DOM shadowRoot , null . shadowRoot attachShadow() , , shadowRoot - . Shadow DOM, .


<slot> slotchange , Light DOM, . , .


<slot> assignedNodes , DOM- Light DOM, . DOM- {flatten: true} , ( ).


Light DOM assignedSlot , , .


(HTML Templates)


, HTML.


Shadow DOM . - , innerHTML , - .


<template> , HTML Templates. , , . , DOM-, , <script> , <link> , , querySelector .


DOM- <template> content , DocumentFragment , . , appendChild .


, Shadow DOM: .


, «» . : , . , DOM-, . .


HTML- (HTML Imports)


. , , , , . HTML- : HTML-, (, , ) <link rel="import" href="x-spoiler.html"> .


, html- <template> <script> . , , <script> . html- , , : , , DOM , , document.getElementById . DOM- html- import link . <link> id, , import template , . <script> const ownerDocument = document.currentScript.ownerDocument , , , <template> ownerDocument , document . . !


, . , ( ), <link rel="import"> . . , , , .


緎習する



. HTML- :


  1. « ». — . app.html , , ? , 20 . , , , , . , . , , .
  2. , . 100 , , n- . , , , .
  3. , HTTP/2. HTTP/2 , .
  4. document.currentScript . , IE11 . , , id id, <template> , , , .
  5. Firefox HTML- , . , - , Firefox, , .

, , . , . , HTML-. .



, IE , Edge 12 Safari 7- . .


v1 ( Chrome 54+ , Safari 10.1+ ) DOM v1 ( Chrome 53+ , Safari 10+ ). .


WebComponents/webcomponentsjs . , , webcomponents-sd-ce.js , DOM, HTML-.


, , : , — ShadyDOM — ShadyCSS DOM. . : <template> .


— Promise , CustomEvent , Object.assign() , Array.from() . - , webcomponents-platform es6-promise , polyfill.io .


Element.prototype.insertAdjacentHTML , . , - , issue . UPD: .


, , . ( ) .


WebReflection , ( Firefox). , , , .


, Babel , .


, :



,



. . :



:



, , . , .



— . Babel extends , Chrome , , ES5-. , custom-elements-es5-adapter.js , , . , . ES5-, - ( ), ( ).


DOM , :


 <div id="custom-elements-adapter-test"> <script> (function() { if (isNative(window.customElements.define) === false) { // ,  Element.prototype.remove()   , //        , //  .parentNode.removeChild() document.getElementById("custom-elements-adapter-test").remove(); } function isNative(fn) { return (/\{\s*\[native code\]\s*\}/).test('' + fn); } })(); </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.22/custom-elements-es5-adapter.js"></script> </div> 

, — . — , . — , .


babel-plugin-transform-custom-element-classes . es5-adapter , IE11 , Reflect.construct . , , , . , , — . : , babel-polyfill . . , .


— babel-plugin-transform-builtin-classes WebReflection . , , IE11 - , . , WebComponents . — WebReflection, WebComponents . .


, , — Babel 7, . . , Chrome ( ), IE11, Reflect.construct .


: DOM ES5-, , . , — .


constructor : IE Safari HTMLUnknownElementConstructor , . .


.


DOM


, . -, ShadyCSS.prepareTemplate() , , — , . <template id="x-spoiler"> <x-spoiler> , ShadyCSS.prepareTemplate(document.getElementById("x-spoiler"), "x-spoiler") .


: ShadyCSS.styleElement(this) ;


:host() : :host(.zot) :host(.zot:not(.bar)) , :host(.zot:not(.bar:nth-child(2))) — .


::slotted : . , ::slotted(span) , .header ::slotted(span) .


.



— <template> . , DOMContentLoaded , , customElements.define() , DOM-, content template . : customElements.define() DOMContentLoaded , customElements.define() . . 次のようになりたす。


 try { HTMLTemplateElement.bootstrap(document); } catch (e) { } 

<template> DOM , : <template> DOM- . .


<script> : , <template> - : IE . . , - .


gulp


, , : html . <script> , . ( ) <template> , DOM HTML. <template> id , . DOM, <style> , CSS. , HTML , , <style> , css.


, , . これには次のものが必芁です。



gulp , , gulpfile , . github . :


src . html - (, ), scaffolding.js js- components -.


index.html @@templates , -, -, , es5- , app.js -, .


scaffolding.js , DOM ( ).


. : .


scripts <script> app.js :


 gulp.task("scripts", () => //    gulp.src("src/components/*.html") //       .pipe(concat("app.js")) //  .     , //  jsdom,    api //    <script>     .pipe(insert.transform(content => { const document = (new JSDOM(content)).window.document; const scriptsTags = document.querySelectorAll("script"); const scriptsContents = Array.prototype.map.call(scriptsTags, tag => tag.textContent); return scriptsContents.join(""); })) //   scaffolding.js .pipe(gap.prependFile("src/scaffolding.js")) //    Babel .pipe(babel({ presets: ["env"] })) //   .pipe(gulp.dest("dist")) ) 

templates , <template> :


 gulp.task("templates", () => //      gulp.src("src/components/*.html") //      jsdom,    <template> //     data-, ,    //  .     .pipe(insert.transform((content, file) => { const componentName = path.basename(file.path, ".html"); const document = (new JSDOM(content)).window.document; const templatesTags = document.querySelectorAll("template"); templatesTags.forEach(template => template.setAttribute("data-component", componentName)); const templatesHTML = Array.prototype.map.call(templatesTags, tag => tag.outerHTML); return templatesHTML.join(""); })) //        .pipe(concat("templates.html")) //   gulp-html-postcss,     html  <style> .pipe(postcss([ autoprefixer() ])) //   —  html  css .pipe(htmlmin({ collapseWhitespace: true, conservativeCollapse: true, minifyCSS: true, })) //   .pipe(gulp.dest("dist")) ); 

html- @@templates templates.html , templates . gulp, . , php include . gulp .


, , data -? Gulp . , , ShadyCSS.prepareTemplate() . , scaffolding.js :


 document.querySelectorAll("template[data-component]").forEach(template => { ShadyCSS.prepareTemplate(template, template.dataset["component"]); }); 

以䞊です -, gulp . .



, . , , HTMLElement . x-component , , . ( , ):


$ $$ . querySelector querySelectorAll Light DOM. Chrome DevTools, jQuery .


fireEvent , . CustomEvent , , dispatchEvent . , c .


 //  this.dispatchEvent(new CustomEvent(`x-timer.ended`, { bubbles: true })); //  this.fireEvent(`x-timer.ended`); 

is localName , — . , , ( ).


getTemplateCopy - id .


makeShadowRoot DOM. , id ( this.is ) getTemplateCopy . DOM ( ShadyCSS.styleElement(this); ), . $ $$ shadowRoot .


 //  ShadyCSS.styleElement(this); this.attachShadow({ mode: "open" }); const template = document.getElementById("x-spoiler"); const templateClone = template.content.cloneNode(true); templateClone.querySelector("button").textContent = this.text["when-close"]; this.shadowRoot.appendChild(templateClone); //  this.makeShadowRoot(); this.shadowRoot.$("button").textContent = this.text["when-close"]; 

. , properties , , / , ( ), . , :


 // ,  get opened() { return this.getAttribute("opened") !== null; } set opened(value) { if (!!value) { this.setAttribute("opened", ""); } else { this.removeAttribute("opened"); } } // ,  properties = { opened: { type: Boolean, }, } 

, - .


- , .


, — , , ( ).


,


, , , - IE11, , ? Chrome, , es5- es5- -. .


, JS , - ( — ES), . , — . ? , . , , , 2018 75%. .


-, , ES5 ES6 . , , Babel scaffolding.js . :


 gulp.task("scripts-es5", buildJS.bind(null, "es5")); gulp.task("scripts-es6", buildJS.bind(null, "es6")); function buildJS(mode) { return gulp.src("src/components/*.html") .pipe(concat(`app-${mode}.js`)) .pipe(insert.transform(content => { const document = (new JSDOM(content)).window.document; const scriptsTags = document.querySelectorAll("script"); const scriptsContents = Array.prototype.map.call(scriptsTags, tag => tag.textContent); return scriptsContents.join(""); })) .pipe(gulpif(mode === 'es5', gap.prependFile("src/scaffolding.js"))) .pipe(gulpif(mode === 'es5', babel({presets: ["env"]}))) .pipe(uglify()) .pipe(gulp.dest("dist")) } 

, app-es5.js app-es6.js . , . , ́ , .


. , 150 ! . , , . , . . html:


 (function () { var wcReady = ("attachShadow" in document.documentElement) && ('customElements' in window); var scripts; if (wcReady) { scripts = [ "./app-es6.js" ]; } else { scripts = [ "https://cdn.polyfill.io/v2/polyfill.js?features=default", "https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.22/webcomponents-sd-ce.js", "https://cdn.jsdelivr.net/npm/template-mb@2.0.6/template.js", "./app-es5.js" ]; } scripts.forEach(function (script) { insertScript(script); }); function insertScript(src) { var script = document.createElement('script'); script.src = src; script.async = false; document.head.appendChild(script); } })(); 

. : , - bower es-5 . , , .


.


ポリマヌ


-, Polymer . , - - Polymer.Element . , -, .


, -, Polymer. - webcomponents.org Polymer, . , Polymer.


Polymer , , Polymer, , -. Polymer -, , , jQuery DOM JS.


Polymer HTML-.


Polymer, -, , .


Polymer API . , , , HTML- ( ES6 ), HTML- ( ). , , . , .


Polymer, . , , , Polymer.



, — , , . . . , ( , ) . , , PostCSS , . — IDE PostCSS- <style> . .


, node-sass . , <style></style> , .


結論


— , , , -. , - , - .


, (, ::part ::theme DOM ). - — , . - Polymer — , - Polymer, .


資源




Source: https://habr.com/ru/post/J346670/


All Articles