この問題を修正するために仮想DOMをパッチすることに何か問題や難しい点はありますか?
プルリクエストは歓迎します。試しましたが、単純に難しすぎました。
非常に興味深いですね!vdomのフォークに移行するのは理想的ではありませんが、リグレッションがないことを証明できれば(そして、それを独自のGitHub組織に移行できれば)検討できるかもしれません。
vdomテストスイートを実行して、リグレッションがないことを確認できましたか?このプリペンド動作の新しいvdomテストを追加するのはどのくらい簡単でしょうか?
まず、2番目の質問にお答えします。このプリペンド(先頭に追加)の動作は基本的に同じで、余分な作業が省かれます。パッチ適用前と適用後のvdomの動作を説明します。
投稿が20件あり、スクロール後に10件が追加で読み込まれると仮定します。
元々 vdomは次のような一連の変換を行います。
[古い20件] → [古い20件、新しい10件] // 末尾に新しい10件の要素が追加されます
[古い20件、新しい10件] → // 全30件の要素が削除されます
→ [新しい10件、古い20件] // 全30件の要素が適切な場所に挿入されます
パッチ適用後 は、一連の動作は次のようになります。
[古い20件] → [古い20件、新しい10件] // 末尾に新しい10件の要素が追加されます
[古い20件、新しい10件] → [古い20件] // 末尾の新しい10件の要素が削除されます
[古い20件] → [新しい10件、古い20件] // 新しい10件の要素が先頭に追加されます
ご覧の通り、まだ余分な作業はありますが、原則としてそれらの要素をすぐに先頭に追加できます。しかし、この方法なら1か所にコードを追加するだけで済み、既存のコードを書き直す手間が省けます。
各変換はDOM操作になるため、元々の動作では古い要素が再挿入され(これにより再レンダリングがトリガーされる)、YouTube動画が再生されますが、パッチ適用後は要素がそのまま配置されるため、動画は再生され続けます。
virtual-dom の package.json からの抜粋です。
"scripts": {
"test": "node ./test/index.js | tap-spec",
"dot": "node ./test/index.js | tap-dot",
"start": "node ./index.js",
"cover": "istanbul cover --report html --print detail ./test/index.js",
"view-cover": "istanbul report html && opn ./coverage/index.html",
"browser": "run-browser test/index.js",
"phantom": "run-browser test/index.js -b | tap-spec",
"dist": "browserify --standalone virtual-dom index.js > dist/virtual-dom.js",
"travis-test": "npm run phantom && npm run cover && istanbul report lcov && ((cat coverage/lcov.info | coveralls) || exit 0)",
"release": "npm run release-patch",
"release-patch": "git checkout master && npm version patch && git push origin master --tags && npm publish",
"release-minor": "git checkout master && npm version minor && git push origin master --tags && npm publish",
"release-major": "git checkout master && npm version major && git push origin master --tags && npm publish"
},
これらのうち、‘test’、‘dot’、‘cover’、‘view-cover’、‘browser’、‘phantom’、‘travis-test’ がテストに関連しているようです。
‘browser’、‘phantom’、‘travis-test’ は、私のコード内の新しい JavaScript の構文により解析エラーが発生します。他のものはパスします。このコードを次のように変更すると、
var prepend = simulate.every(item => item && item.key)
prepend &= aChildren.every((item, i) => item.key === bChildren[i + shift].key)
このコードに
var prepend = true
for (var i = 0; prepend && i < simulate.length; i++) {
prepend = simulate[i] && simulate[i].key
}
for (var i = 0; prepend && i < aChildren.length; i++) {
prepend = aChildren[i].key === bChildren[i + shift].key
}
すると、すべてパスします。JavaScript を一貫して古いものにするためにこの変更をプッシュできます。これは、これらのスクリプトすべてを満足させることが望ましい場合です。
お願いします - 既存のテストスイートを機能させましょう
virtual-dom を GitHub - discourse/virtual-dom: A Virtual DOM and diffing algorithm にフォークしました - このリポジトリに対してプルリクエストを作成していただけますか?
また、virtual-domへの変更は非常に限定的であることも指摘しておく必要があります。具体的にはプリペンドを対象とし、それ以外の場合は元のアルゴリズムにフォールバックします。そして、その元のアルゴリズムは、一般的に問題を見るとまだ不完全です(古い要素に不必要に触れる例を考えるのは難しくありません)。一方で、アペンド、削除、単一挿入は問題なく処理します。そして、これではストリームの後にブレークアウトするには非常に凝ったことをする必要があります。したがって、実際的な問題としては、一般的に問題を解決することは少しやりすぎかもしれませんが、もちろん、そうすることでより安心して眠ることができます。
新しいテストをプッシュしました。近いうちにPRをレビューする予定はありますか?
アレクセイさん、ありがとうございます。来週中にレビューできるよう努めます。
仮想DOMのフォークを設定し、手動テストでのiframeの問題が解決することを確認しました。
@Aleksey_Bogdanov、しかし、何かお手伝いいただけないでしょうか。Discourse内にテストを追加しようとしています。具体的には:
-
<span>ElementOne</span><span>ElementTwo</span>をレンダリングします。 -
ElementTwoを削除し、
PrependedElementを先頭に追加します。 -
結果が
<span>PrependedElement</span><span>ElementOne</span>であることを確認します。 -
元のElementOneのspanが最終的なElementOneのspanと等しいことを確認します(つまり、再レンダリングされていないことを確認します)。
残念ながら、(4)のチェックが失敗します。これはElementOneが再レンダリングされたことを意味します。このケースで新しいロジックが機能しない理由について、何か考えはありますか? ![]()
新しいフォーク構成と失敗したテストを既存のPRにプッシュしました。
はい、以前この件について書きました(https://meta.discourse.org/t/paused-finished-youtube-videos-automatically-start-playing-when-scrolling-up-far-enough-to-load-new-posts/57692/50?u=aleksey_bogdanov)。
これは一般的に機能するとは期待していません(そして、あなたが説明したテストもパスしないでしょう)。元々は、追加、削除、単一の挿入(おそらくそれらの組み合わせ)で機能していました。私の変更により、純粋なプリペンドでも機能するようになり、これはYouTubeのバグをカバーするはずです。あなたのテストには「削除+プリペンド」が含まれています。これは私の変更ではカバーされていません。
より堅牢なソリューションには、少なくともvirtual-domの「reorder」と「diffChildren」の大幅な書き直しが必要になります。試すことはできますが、virtual-domの独自のフォークを長期的に維持する予定はありますか?という疑問が生じます。堅牢性を目指すのであれば、類似の活発にメンテナンスされているライブラリに切り替えることを試みる方が、より少ない時間と労力で済むかもしれません。他のライブラリはすでにこの問題を解決していると推測します。
なるほど、それは理にかなっています。しかし、「純粋な先頭追加」にしても同じ失敗が発生します。関連する reorder() 関数にブレークポイントを設定すると、bFree.length === bChildren.length のチェックでバグが発生することがわかります。
更新されたテストをブランチにプッシュしました。
これは間違いなく短期的な解決策です。すでに vdom の使用を Ember/Glimmer レンダリングに置き換える作業を開始しています。目標は、今後12か月以内にポストストリームの実装を置き換えることです。Glimmer はこのような「先頭追加」を正しく処理します。
したがって、「不完全ではあるが以前よりは良い」変更をマージすることには全く問題ありません。しかし、もしそれほど手間がかからないのであれば、このテストが機能しない理由を理解できると嬉しいです ![]()
もちろんです。まだわかりませんが、できるだけ早く調査します。
更新。
テストをパスさせるためにキーを追加しました。キーは、どの要素がどの要素に対応するかを virtual-dom に示唆します。キーがないと、virtual-dom は要素が以前と同じ順序に従っていると仮定するだけで、要素の追加を処理していることを認識できません。
次に、virtual-dom は単一の挿入(および単一の追加は特別なケース)を既にカバーしていたため、違いが生じるはずがないので、単一の追加を複数の追加に変更しました。
上記はこれまでにプッシュした変更です。
しかし、現在、複数の追加は驚くべきことに違いを生みません。私の当初の仮説と、それによって私のパッチが機能する理由が間違っている可能性があります。そのため、まだ解明しようとしています。
David、ブラウザでテストをデバッグしたことに気づきました。セットアップは難しいですか?テスト用のデバッガーをセットアップするのに苦労しているので、いくつかポインタを教えていただけると幸いです。
なるほど、わかりました。ありがとうございます!
動作するDiscourse開発環境はありますか?もしあれば、それを起動し、ブラウザで/testsにアクセスしてください。その後、上部にあるフィルターUIを使用して「avoids rerendering on prepend」を検索できます。その後、ブラウザの開発者ツールを使用してデバッグできます。(例:ソースタブに移動し、Ctrl+Pでファイルを開き、vdom/diffを検索してブレークポイントを設定します)
ありがとうございます。試してみます。
したがって、このコード
assert.strictEqual(elementOneBefore, elementOneAfter);
は、どちらの場合でも elementOneBefore がこれらの変更を通じてそのアイデンティティを維持するため、違いはありません。
ご自身で確認したい場合は、簡単な例を以下に示します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Youtube Bug Demo</title>
</head>
<body>
<script>
let iframe = document.createElement("iframe");
iframe.width = 690;
iframe.height = 388;
iframe.src = "https://www.youtube.com/embed/Xc5rB-0ZBcI";
iframe.title = "Strange S.T.A.L.K.E.R. car glitch";
document.body.appendChild(iframe);
let button = document.createElement("button");
button.type = "button";
button.innerHTML = "Reinsert";
button.onclick = function(){
let iframeStart = document.querySelector("iframe");
document.body.removeChild(iframeStart);
document.body.insertBefore(iframeStart, button);
let iframeEnd = document.querySelector("iframe");
alert(iframeStart === iframeEnd);
};
document.body.appendChild(button);
</script>
</body>
</html>
違いを生むのは ‘removeChild’、‘insertBefore’ の呼び出しなので、DOM変更のチェックを追加しました。これで、以前のバージョンではテストが失敗し、現在のバージョンではパスするようになります。これで十分であることを願っています。
素晴らしい、@Aleksey_Bogdanov さん、ご尽力いただきありがとうございます。PRをマージしました。1時間以内にMetaで公開されます。
