Unsplash の Andre Taissin によるソーシャル メディア写真
DOM 組み込み要素を拡張する最も簡単な方法:
ポリフィルは必要ありません。最新のブラウザはすべて動作します™️
HTML 、 SVG 、またはMathMLなどのその他のカスタム名前空間を問題なく拡張できます。
要素は最初から作成することも、適切な水分補給のためにオンデマンドでアップグレードすることもできます
313 バイト(コア) またはカスタム要素のライフサイクル コールバックを含めた774 バイトに収まります (新しく学ぶことは何もありません)
この例はライブでテストでき、この非常に小さいながらも強力なモジュールで何ができるかの表面をなぞっただけです。
// const createRegistry = require('nonchalance');import createRegistry from 'nonchalance/core';// デフォルトのエクスポートは、オプションで `globalThis` のような // ネイティブ DOM を持たないすべての環境のコンテキストを受け入れます。/ / このようなレジストリは、要素の作成に使用される修飾クラス名と `document` 参照を公開できます。// const {HTML} = createRegistry();//グローバル コンテキストクラスと何も共有しないレジストリからカスタム要素を作成またはアップグレードするには、任意の要素を拡張します。// Password extends HTML.Input { // 静的タグフィールド = "input" を継承します コンストラクター(...args) {super(...args);this.type = 'パスワード'; } // 悪意のあるスクリプトがパスワードを簡単に取得することを回避します get value() {return '********'; } // デモの場合: パスワードが設定されている場合でもネイティブの `value` を使用します 値を設定(シークレット) {super.value = シークレット; }}document.body.innerHTML = ` <form> <input type="text" name="user" placeholder="user"> <input type="password" name="password" placeholder="password"> <input type="submit"> </form>`;// new ClassTypenew Password(document.querySelector('[type="password"]'));// または新しいインスタンスを作成しますconst secret = Object.assign(新しいパスワード, { 名前: 'パス'、 値: Math.random()、 プレースホルダー: '実行時パスワード'});const form = document.querySelector('form');form.insertBefore(secret, form.lastElementChild);
カスタム関数モジュールを使用すると、自然class extends HTMLSomethingElement {}
たびに表示される不正コンストラクターエラーに直面することなく、あらゆる種類の要素をアップグレードできます。このエラーは、そのようなクラスがエントリとしてグローバルに定義されていない場合に発生します。 customElements
レジストリ。
このモジュールを通じてグローバル コンテキスト上でグローバルに共有されるものは何もないだけでなく、組み込みの拡張機能を機能させるための面倒な追加作業はまったく不要です。
新しい要素または渡された要素は常にプロトタイプのルート チェーンを保持します。
余分な属性や名前の衝突が発生することはありません
さらに、各モジュールまたはプロジェクトごとにHTML レジストリを作成してコンポーネント間で共有できるため、少なくとも createElementNS を公開するdocument
フィールドを備えた、偽のまたはモックされたglobalThis
のような環境をそのようなレジストリ作成に渡すこともできcreateElementNS(namespace, tagName)
。 createElementNS(namespace, tagName)
メソッド、およびプロジェクトがテストする 1 つ以上のクラス ( HTMLElement
や、プロジェクトが成功するために必要なその他のクラスなど)。
ただし、このモジュールの主なターゲットはDOMであるため、 globalThis
参照は適切なデフォルトとして使用されますが、それでもデフォルトのエクスポートによって作成されたレジストリに関して何も共有されるわけではありません。
いいえ。 custom-function
仕組みは次のように要約できます。
# a native <p> protoype chain HTMLParagraphElement -> HTMLElement -> Element -> Node # a <p> passed to new (class CustomP extends HTML.P {}) CustomP -> HTMLParagraphElement -> HTMLElement -> Element -> Node # a <p> passed to class AnotherP extends CustomP {} AnotherP -> CustomP -> HTMLParagraphElement -> HTMLElement -> Element -> Node
簡単に言うと、 new AnotherP
を介して要素を作成するか、 new AnotherP(liveParagraph)
を介して要素をアップグレードすることは、プロトタイプの継承チェーンに保持されるため、要素が DOM を離れたり、ネイティブの性質を変更したりする必要がなく、単にプロトタイプ チェーンを更新するだけです。 。
概要:何気ないレジストリは、ネイティブの組み込み拡張機能が内部で機能するのとまったく同じ方法で、性質を変更せずに要素をアップグレードするだけです。
はい、 /jsx
エクスポート経由または/ref
エクスポート経由のいずれかです。
/jsx については、 「/jsx エクスポートとは何ですか?」を参照してください。セクション。
/ref については、 「/ref エクスポートとは何ですか?」を参照してください。セクション。
/ce
エクスポートは、クラスのconnectedCallback
、 disconnectedCallback
、およびattributeChangedCallback
メソッドとその静的なobservedAttributes
フィールドと互換性のある方法で要素を自動的にアップグレードします。
このモジュールは、すでにうまく機能しているカスタム要素モジュールの微調整されたバージョンを使用します。
それがどのように機能するかを理解するには、codepen のこのライブデモをご覧ください。
いいえ。比喩的に言えば、 HTML要素には、セマンティックな意味と、一度有効になると明確に定義された望ましいユーティリティの両方があります。これはObject.setPrototypeOf(() => {}, Number.prototype)
であっても、 JS関数が永久にJS関数であり続けるのと同じです)。 Object.setPrototypeOf(() => {}, Number.prototype)
が発生します...それがどれほど間違っているかがわかりますか、または同意できますか?
このモジュールは、その機能の誤用を防ぐつもりはありません (また、おそらくできない) ため、要素がアップグレードされるたびに、そのネイティブ プロトタイプ チェーンが舞台裏で保持されるようにしてください。そうしないと、単独でDOMと戦うことになります。 ..私に言わせれば、これは非常に不便ですか?
つまり、 customElements.define('my-link', class extends HTMLDivElement {}, {extends: 'a'})
意味をなさないのと同じように、このモジュールはユーザーを信頼しており、意味のないクラスは回避されることが望まれます。
現在、このモジュールのデフォルト/メイン エクスポートは、まったく同じ/core
エクスポートを指します。
このモジュールはそのシンプルさと Vaporware コード サイズの点でパンドラの箱を開けるものであり、主にまだ0.
バージョンよりも古いため、インデックスに何を含めるべきかを検討中です。ここで私の考えをいくつか述べます。
このように定義されたコンポーネントを理解する ESX ベースのモジュールがあれば素晴らしいと思いませんか?
このモジュールを通じてコンポーネントを作成するJSXプラグマ関数があれば素晴らしいと思いませんか?
... (ここにあなたのプレースホルダ) ... があるのはクールだと思いませんか?
はい、それは素晴らしいでしょう。デフォルトのエクスポートにどのような名前を付けるかを決めることができたら、他の良い名前の中でも特にその名前をこのモジュールのデフォルトのエントリとして導入するつもりです...期待してください、または私に与えてくださいそれを行う方法についての考えとヒント
ただし、それまでは、将来の更新によってロジックが台無しにならないように、明示的なエクスポートを使用してください。最近の変更でご迷惑をおかけした場合は申し訳ありませんが、それが良いことだったということは簡単に理解できると思います。
要素が離れた場所でアップグレードされる場合、アクセサーを通過できない何らかのプロパティがアタッチされている可能性があります。
このヘルパーは、継承されたプロパティが独自の要素キーとして削除され、その後すぐにアクセサーとしてトリガーされるようにするだけです。
import createRegistry from 'nonchalance/ce';import アクセサ from 'nonchalance/accessors';const {HTML} = createRegistry();class WithAccessors extends HTML.Div { コンストラクター(...args) {accessors(super(...args)); } get value() {console.log('get value', this._value);return this._value; } set value(_value) {this._value = _value;console.log('set value', this._value); }}// ネイティブ div elementconst div = document.createElement('div');div.value = 123;// upgradednew WithAccessors(div);// re-checkconsole.log(div.value);
さらにテストするにはライブを見てください。
/builtin
エクスポート (248 バイト) は、バックグラウンドでcustom-function
を使用しない点を除けば、 /core
とまったく同じです。つまり、次のことを意味します。
まだcustomElement組み込み拡張として登録されていない限り、エラーがスローされるため、 new BuiltIn()
またはnew BuiltIn(element)
を実行することはできません。
CodePen のライブ デモで示されているように、コンポーネントの登録を自動化するために使用できます。
このエクスポートに関する唯一の大きな注意点は、実際の標準カスタム要素に基づいているため、Safari または WebKit には組み込みのポリフィルが必要になる可能性があることです。例:
<!-- Safari 専用ポリフィルの最上位ページ スクリプト --><script>self.chrome ||self.netscape ||document.write('<script src="//unpkg.com/@webreflection/custom-elements -builtin"><x2fscript>');</script>
デフォルトでは、 HTML
とSVG
両方の名前空間が組み込みの拡張として許可されていますが、カスタム要素はSVG の拡張を受け入れないため、現在の/builtin
エクスポートでは実質的にHTML の拡張のみが可能であることに注意してください。
./dummy
エクスポートは主にSSRを対象としており、静的tag
フィールドのみを運ぶクラスを拡張するためのまったく同じユーティリティを提供します。
/tag
と組み合わせると、さりげなくSSR100%、遠距離でのハイドレートも可能です。
import createRegistry から 'https://unpkg.com/nonchalance/dummy';import createTag から 'https://unpkg.com/nonchalance/tag';const {HTML} = createRegistry();class HelloDiv extends HTML.Div { ConnectedCallback() {console.log('ここにいます'); }}// 水を取り込むために再利用可能な名前空間を作成しますconst nmsp = {HelloDiv};// タグを作成しますtransformerconst tag = createTag(nmsp);// 代わりにサーバーの応答を想像してください// 注: このコードはデモ専用ですconsole.log( tag`<!doctype html><script type="module">import createRegistry from 'https://unpkg.com/nonchalance/ce';const {HTML} = createRegistry();const nmsp = {};for (const el of document.querySelectorAll('[data-comp]')) { const {comp} = el.dataset.comp を削除します。 ](el);}</script><HelloDiv></HelloDiv>` 。参加する('') 。トリム() .replace('const nmsp = {};',`const nmsp = { ${[...Object.entries(nmsp)].map( ([key, value]) => `${key}: ${値}` ).join(',n')} };` ));
/jsx
エクスポート (976 バイト) は追加のcreateElement
オプションを受け入れ、変換する@jsx プラグマとして使用できるjsx
関数を返します。特に、 ReactまたはPreactでデフォルトですでに動作しているすべてのものや、 HTMLまたはSVGレジストリを介して拡張されたクラスも変換できます。 /ce
これらのクラスにもたらすすべての機能が含まれます: いくつかのスパイスを加えたライフサイクルのようなカスタム要素:
クラスは、要素に沿って渡されたpropsをコンストラクターで受け取り、シグナルや他の関数を有効にしたり、デフォルトのJSXコンポーネントですでに処理できるものを処理したりします。
コンストラクターが呼び出されるとき、要素はすでにその子で埋められており、ドキュメントが解析される前にクラスが定義/登録されるときに標準のカスタム要素で知られる可能性のある不正行為を回避します。
/builtin
extend と同様に、 new Component(props)
はできませんが、 <Component {...props} />
は常に可能です。
実際にReactで使用されている様子を CodePen でご覧ください。
DOM は、間に間接参照がどれほど多くても、 DOMです。フレームワークの機能に応じて DX は異なる場合がありますが、 React を求めている場合は、 nonchalance/coreまたはnonchalance/ceを使用して通常の JSX ノードをプロモートする、小さくてもエレガントなref
ベースの方法があります。
import Referenced from 'nonchalance/ref';// コンポーネントが参照として渡されることを示します// 注: これは、wildconst での使用に関係なく、// クラスの整合性を付与する単なる軽いプロキシです。 Component = Referenced(class extends HTML.ディビジョン { constructor(...args) {super(...args);this.addEventListener('click', console.log); }});ReactDOM.render( <div ref={Component}>クリックして</div>、 ドキュメント.ボディ);
ref
ユーティリティは、通常の非シャランスクラスの機能に影響を与えることなく、デコレーターとしても使用できます。さらに、各要素は 1 回だけアップグレードされるため、コンストラクターにリスナーやロジックを安全に追加できます。
これを試してみるには、codepen 上のこのデモをご覧ください。
/selector
エクスポート (796 バイト) を使用すると、CSS セレクターのライブ定義が可能になり、関連クラスがそのセレクターに一致する要素を自動的にアップグレードします。
このようなセレクターは可能な限り一意である必要があり、そうしないと予期せぬ事態が発生する可能性があることに注意してください。セレクターを最適に説明するには、特定のクラスまたはdata-
属性を使用します。
import createRegistry from 'nonchalance/core';import {define } from 'nonchalance/selector';const { HTML } = createRegistry();const Special =define('[data-comp="special"]'、クラスは HTML を拡張します。ディビジョン { constructor(...args) {super(...args);this.textContent = '私は特別です!'; }});// 「私は特別です!」が含まれます。テキストを一度 livedocument.body.innerHTML = `<div data-comp="special"></div>`;
./tag
エクスポート (188 バイト) を使用すると、ハイドレーションに適した方法でテンプレートを変換できます。
これは、完全に対応したテンプレート リテラル タグの背後にある中間値として使用でき、これらの要素がDOMに到達するとハイドレーションが発生します。
import createRegistry from 'nonchalance/ce';import createTag from 'nonchalance/tag';const {HTML} = createRegistry();class HelloDiv extends HTML.Div { ConnectedCallback() {console.log('ここにいます'); }}// ハイドレートするために再利用可能な名前空間を作成しますconst nmsp = {HelloDiv};// タグを作成しますtransformerconst tag = createTag(nmsp);// 簡単で汚いデモドキュメント.body.innerHTML = tag`<HelloDiv />`.join( '');// ハイドレーション examplefor (const el of document.querySelectorAll('[data-comp]')) { const {comp} = el.dataset; el.dataset.comp を削除します。 // 要素を一度アップグレードします 新しい nmsp[comp](el);}
CodePen でライブをご覧ください。
はい。 /core
と/ce
両方のエクスポートにより、デフォルトでHTMLとSVG の両方のレジストリを作成できます。
import createRegistry from 'nonchalance/core';const {HTML, SVG} = createRegistry();class Circle extends SVG.Circle { constructor(options) {Object .assign(super(), options) .setAttribute('fill', 'gold'); } set cx(value) { this.setAttribute('cx', value) } set cy(value) { this.setAttribute('cy', value) } set r(value) { this.setAttribute('r', value) }}document.querySelector('svg').append( 新しい円({cx: 100, cy: 100, r: 50}));
codepen でライブをご覧ください。
例として{MathML: "http://www.w3.org/1998/Math/MathML"}
を使用して、任意の名前空間をcreateRegistry(options)
に渡すこともできます。
document.createElementNS
にとって意味のある名前空間はすべて許可され、アップグレードできるDOM要素の種類に制限はありません。
これについては以前に非常に長い答えがありましたが、要約すると、このモジュールはW3C 、 WHATWG 、またはECMAScriptによって提供される標準を使用しており、どこでも動作するのに必要な容量は 1KB 未満です。
これはポリフィルではありません。JS ワールドでコンポーネントを作成するのに役立つユーティリティです。コンポーネントが衝突したり、ツールが必要になったり、好きな/必要な/好みのターゲット プロジェクト間で移植できないことを心配する必要はありません。
つまり、フロントエンドとバックエンドの両方の世界に共通のコンポーネントを提供するために 1K バイト未満の追加を許容できるのであれば、適切なモジュールを選択したことになります。
「そんなことしてはいけない! 」と考える人たちに対して泥んこ遊びをする不注意な子供であることほど解放的なものはありません。
このモジュールは、最新の JS 機能が提供する自由を通じてその感覚を何らかの形で表現し、ブラウザ ベンダーや最新の仕様によって提供され、ますます複雑になる代わりに、エレガントでポータブル、そして超軽量の代替手段を示しています。これらはすべて、単純に拡張する機能を開発者に回避させるために必要なものです。組み込みの機能を備えており、Web の特徴であるシンプルさと優れたアクセシビリティの両方を維持します。