大規模なフロントエンドプロジェクトのブレークポイントデバッグ共有と再利用の実践

著者:enoyao、Tencentエンジニア

バックグラウンド

プロジェクトがどんどん大きくなるにつれて、多くのモジュールを維持する必要があるかもしれません。TencentDocumentExcelプロジェクトには10​​を超える大きなモジュールがあり、各大きなモジュールにはN個の小さなモジュールがあり、各大きなモジュールには小さなモジュールがあります。主な担当者は、モジュールの問題のフォローアップです。

これは大きな問題につながります。ほとんどの場合、モジュールの担当者は自分のモジュールの問題にのみ注意を払い、他の担当者のモジュールの特定の問題についてはあまり知りません。

例:コピーアンドペーストに問題があるというユーザーからのフィードバックがあり、問題をすばやく特定したい場合、コピーアンドペーストモジュールの担当者のみが問題に対処できます。コピーアンドペーストモジュールの担当者が休暇を要求した場合は、他の担当者がこの問題に対処する場合、他の責任者がこのモジュールにまったく精通していない可能性があるため、ソリューションのコストは非常に大きくなります。

別の例:新しいクラスメートが何人かいて、ユーザーフィードバックのトラブルシューティングをすばやく行ってほしい場合、このモジュールのデバッグの経験を教えて、慣れ親しんだピットを教えたり、整理したりすることしかできません。対応するiwikiを見せて(一般的に効率が低く、誰にも見られません!)、問題をゆっくりと見つけてもらいます。そうすれば、すべての新入生がモジュールに精通し、学習と保守のコストがますます高くなります。 、プロジェクトが大きくなるほど、この状況は深刻になります。

そのため、少なくともモジュールのメンテナンスコストを削減し、問題のメンテナンスと特定を改善するために、これらの問題を解決する方法について多くのことを考えてきました。

プログラム

上記の問題は非常に苦痛であるため、クロールとローリングの一連のソリューションを徐々に検討してきました。ブレークポイントのデバッグに基づいて、共有および再利用された実用的なソリューションと呼びましょう。ここにキーワードはブレークポイントです。すべての開発者に精通しているのと比較して、フロントエンドとモジュールの配置の問題がある場合、必然的にブレークポイントを使用してコード操作のいくつかの重要な領域を壊します。次に例を示します。

class CopyPaste {
    // 内部粘贴
    pasteFromInter(){ ...}
    // 外部粘贴
    pasteFromOuter(){ debugger; ...}
    // 外部图文粘贴
    isShapePasteFromOuter(){ ... }
    // 外部图片粘贴
    isImgPasteFromOuter(){ ... }
    // 外部文本粘贴
    isTextFromOuter(){ ... }
}

上記のコードは、ユーザーがコピーアンドペーストの問題をフィードバックした場合、モジュールに精通している担当者は、モジュールに精通しているため、モジュールに精通しているため、ブラウザにすばやく表示するため、ユーザーのフィードバックによると外部ペーストに問題があることを知っています。ソースコードのコンソールブレークポイントまたは手動でdebuggerキーワードを挿入して、ユーザーの問題を段階的に特定します。ユーザーはpasteFromOuter、トリガーされた場合は最初に内部貼り付けをチェックし、次に機能isShapePasteFromOuterが正常に実行されていること、パラメーターとパラメーターが正しいこと、コードが正しいかどうかをチェックします。曲がって、行きisImgPasteFromOuterます。

次に、問題のトラブルシューティングと修復を行った後、長いため息をつきます。次の問題が発生した場合は、ブラウザまたはコードで現在のデバッグトレースをクリーンアップし、上記の一連のアクションを何度も繰り返します。ほとんどの学生は、問題のトラブルシューティングや要件の作成時に、上記の同様のアクションを毎日繰り返しています。これらの貴重なデバッグトレースを保存することを検討できますか。また、私たちまたは他の学生が同様のモジュールの問題に遭遇した場合は、これらを実行します。私たちの血と涙を凝縮する精神的な旅は自動的に再び再発しますか?

コードセグメント デバッガーの場所を記録します
pasteFromInter 2行4列
isShapePasteFromOuter 256行89列
isImgPasteFromOuter 867行12列

大規模なプロジェクトの場合、小さなバグのデバッグリンクごとに時間コストが非常に高く、再現や再現も困難です。同じような問題が再び発生したときに、同じようなものを再利用することができます。デバッグ経験。怪我の痕跡や経験がありますが、問題が再び発生したときは、もっと自信を持って落ち着く必要があります。

したがって、私たちの最初のタスクは、実際には貴重なデバッグリンクを保持すること、つまり、数え切れないほどの昼と夜、心の奥深くに突き刺さるすべてのブレークポイントを保持することです。

プラグイン

実践の過程で、数え切れないほどの方法を試しました。最初の解決策は、ブラウザプラグインに基づいてブレークポイント保持を実装することです。これは、Google Chromeプラグイン開発によって提供されるインターフェイスに基づいてchrome.debugger、Chromeリモートデバッグプロトコルのメッセージ送信方法です。chrome.debugger1つ以上のタブにアタッチして、JavaScriptをデバッグできます。そして、debuggeeを使用して、sendCommandとonEventに基づいてプラグイン通信を実行します。これにより、プラグインのページをデバッグできます。多くのプラグインとツールは、このプロトコルに基づいてブラウザコンソールと通信します。このソリューションは、ブラウザ自体のデバッグと同様に、リモートデバッグパネルのみを実装できます。インターフェイスはコードをロードしてブレークポイントを記録し、最終的にこれらのブレークポイントを共有できます。

この種のソリューションエクスペリエンスはさらに悪化します。まず、プラグイン自体によって実装されるデバッグパネルはGoogle Chromeほど優れていません。次に、プラグインを積極的に開発してインストールする必要があります。共有の前提は、両方の当事者が対応するプラグインをインストールし、開発し、宣伝する必要があることです。コストが高いので個人的にはお勧めしませんが、プラグインは別の実現に基づくこともできるので、このプログラムがどこにも通じないという意味ではありませんdebug次の機能プログラムです。

デバッグ機能

関数ブレークポイント debug(functionName)undebug(functionName)メソッドを使用して詳細に説明します。ここで、デバッグ関数はfunctionNameに対するものです。debug()コードに挿入でき(このメソッドとconsole.log()ステートメントは似ています)、DevToolsコンソールから呼び出すこともできます。debug()これは、関数の最初の行にコード行ブレークポイントを設定することと同じです。

通常、コンソールで使用されますが、プラグインchrome.devtools.inspectedWindow.evalはブラウザインターフェイスを備えたメソッドを使用するため、ブレークポイント関数を自動的に発行するためにコンソールにコードを挿入して実行できるため、プラグインを使用したこのメソッドの方がエクスペリエンスが向上します。

chrome.devtools.inspectedWindow.eval(
  `debug(window.xxxApi);`,
  (value) => {
    callback && callback(value);
  }
);

しかし、注意深い学生は、私がdebug関数モニターを使用するのはグローバル関数window.xxxApiであることに気付くので、ここで経験を要約します。このアプローチの欠点は、コンソールを使用すると、コンテキスト内の関数を検索するため、通常、グローバル関数管理にのみ使用できます。点線の関数がコンテキストにない場合は、手動で対象関数のスコープにブレークポイントを設定し、その関数を使用してトリガーする必要があります。クロージャー関数の場合、方法はありませんが、欠陥は隠されていません。 、このメソッドは、コードが混乱している場合でも、グローバル関数をすばやく見つけるのに役立ちます。コードをすばやく読み取って関数ブレークポイントを追加できるため、場合によっては、このソリューションを代替手段として提案します。奇跡の効果が出ます!

AST注入

上記のさまざまな落とし穴を経験した後、実装した一連のソリューションを簡単に紹介しましょう。

私たちのプログラムは、実際には、改善に基づいて作成された関数呼び出しチェーンソリューションの前にあります。コードdebuggerへの独自のコード入力キーワードを開発できるため、どこでも実行できます。このツールを機能させてみませんか?

まず、ステートマシンを使用して、一般的に使用されるホイッスル構成テーブルと同様に、RBIの場所を配布する必要がある場所をツールに指示できます。

Module 'CopyPaste'
    index.ts -f pasteFromInter -s !(()=>{ console.log(window.Worker) })()
    index.ts -f pasteFromOuter -s console.log('success') -check messagecenter1
    index.ts -f isShapePasteFromOuter
End Module
  • Module <-- state --> End Module ここでは、ブレークポイントを分散する動作である状態について説明します。これは、そのタイプのモジュールを監視するために使用されます。たとえば、コピーアンドペーストモジュール、データレイヤーモジュール、またはデータレイヤーモジュールです。

  • -f functionname -s codeこの状態は、本明細書では、pasteFromInter分布関数のブレークポイント、および注入されたdebuggerコードなどの特定の動作特性について説明することができる

webpackでは、ローダーまたはプラグインの2つのプロセスでこの構成ファイルを解析できます。ここでは、サードパーティのライブラリまたは通常のライブラリを使用して、上記のステータステキストを解析することもできます。私は、グローバルディレクトリ内または.debug.json上記の状態を書き込むためのモジュール定義内でローカルにこの状態テーブルIを解決するローダーでした。次に、マップオブジェクトが解析されます。

args = argument({
    "--class": String, // 类
    "--function": String, // 函数
    "--code": String, // 函数
    "-c": "--class", // 转义替换
    "-f": "--function",
    "-s": "--code",
  },{ argv: debugConfigValue, }
);

ステートマシン構成ファイルの方法を記述したくない場合、実際には、debug.jsonファイルを使用してブレークポイントの場所を記述することができます。このアプローチはより単純で、コスト解析jsonファイルはステートマシンの構成ファイルの多くであるjsonファイルよりも低くなります。ここに含まれる主なフィールドは、検出する必要のあるコードのパス、ファイルを見つけるためのこの便利なツール、次に検出する必要のあるクラスまたは関数の名前、コードの場所を見つけるためのこの便利なツール、および検出項目の名前と検出する必要があるものです。コード、およびキーキー値:

{
  "MessageCenter": {
    "function": [
      {
        "path": "src/core/network/message-center/SendMessageCenter.ts",
        "name": "_sendUserChanges",
        "title": "数据层断点测试2",
        "code": "__console.log('数据层断点测试2')",
        "key": "MessageCenter|function|1"
      }
    ]
  }
}

キーはここで明確なポイントに関連して定義できます。そのようなMessageCenter|function|1手段はMessageCenterファイルモジュール内のRBIの機能であり、将来も改善を続けることができますMessageCenter|class|1:12。つまり、モジュール内の特定のドット位置MessageCenterファイルの特定のクラスを意味します。このキーのセマンティクスが豊富な場合、後続の配布はより正確になり、配置の問題はより効率的になります。詳細は、ビジネスシナリオに従って定義できます。

class CopyPaste {
    // 内部粘贴
    pasteFromInter(){
        debugger
        ...
    }
}

構成ファイルがある場合は、侵入せずにデバッグコードと検出コードをコードに追加する方法を検討する必要があります。ASTを介して挿入することをお勧めします。これにより、消去など、コードの主要部分をツリーに分類できます。コロン、括弧、セミコロンなどを使用すると、重要なノードに焦点を合わせることができます。上記のコードを解析すると、次のAST構文ツリーが取得されます。

{
  "program": {
    "type": "Program",
    "body": [{
      "type": "ClassDeclaration",
      "id": {
    
    { "type": "Identifier", "identifierName": "CopyPaste" }, "name": "CopyPaste" },
      "body": {
        "type": "ClassBody",
        "body": [{
            "type": "ClassMethod",
            "key": { "type": "Identifier", "name": "pasteFromInter" },
            "body": { "type": "BlockStatement", "body": [{ "type": "DebuggerStatement" }]},
            "leadingComments": [{ "type": "CommentLine", "value": " 内部粘贴" }],
        }]
      }
    }]
  }
}

おそらく、具体的な手順は次のとおりです。MessageCenter|function|1この構成パラメータの文字列を解析して関数名、モジュール名、場所情報などを取得し、コードと構文および字句解析、構文ツリーASTをスキャンして、解析したばかりの関数に従って取得します。 ASTツリーノードに一致する名前、モジュール名、場所情報、それにデバッグおよび検出コードを追加し、最後に私たちが処理したコードを出力します。

上記の原則は誰もが知っています。プラグインを使用してWebpackツールに実装できます。プラグインでは、ビジターモードを使用することがよくあります。つまり、特定のパスにアクセスすると、それを照合してから、上記のpasteFromInter関数など、ノードのこの変更は、ClassMethod生成されたコードがASTツリーの訪問をプラグインします。訪問者は対応する字句特性に一致できます。ここではすべてに一致しClassMethodてからパスを取得できます。関数名、関数引数、場所の関数などのノード情報に対応して、キー情報を取得するために、試運転およびテストコードが注入された処理ノードの機能、またはdebugger中断するための直接注入実行できます。ポイント。

plugins = {
  // 访问器
  Visitor = {
      'ClassMethod'(path) {
        // 检点
        path.node
      }
  }
}

もちろん、ClassMethod同様の構造を構築するには注入検出コードが必要です。@babel/types最も簡単な方法は次のように、コードをすばやく注入するためのツールでできることdebuggerです。

types.expressionStatement(types.identifier(`debugger`))

これにより、一致したパスの特定の場所に1つが配置され、debuggerコードソースファイル自体はまったく変更されていませんが、コードの一部はASTツリーと構成ファイルを介して指定された場所に正常にマージされています。もちろん、実際の状況は配信の場所が関数内の特定の位置ではなく、クラス関数内の特定の位置、クロージャー関数内の特定の位置である可能性があるため、予想よりも複雑です。したがって、さまざまな文法構造と互換性がある必要があります。 ASTでこれらの関数のすべての機能を照合することによってのみ、コードを正確に配信できます。この関数を例として取り上げて、考慮する必要のある状況のいくつかをリストしてみましょう。

  • FunctionExpression

これらの2つの書き込み方法を満たす必要があります。そうしないと、デバッガーが間違った位置を送信します。

this.xxx = function() { debugger }
const xxx = function() { debugger }
  • ClassMethod

この一般的な状況は次のように特定できますが、プライベート関数など、より正確にしたい場合は、より正確なアクセサーを作成する必要があります。

class xxx { xxx:(){ debugger } }
  • FunctionDeclaration

上記の関数式の記述に加えて、関数には宣言定義の記述もあることを忘れないでください。これは完全である必要があります。

function xxx() { debugger }
  • ArrowFunctionExpression

最後に、下矢印関数の記述を検討してください

const xxx = () => { debugger }
this.xxx = () => { debugger }
class xxx { xxx = () => { debugger } }

ほとんどの場合、マッチング機能はプロジェクトによって発行されたデバッグコードのほとんどのシーンをカバーできますが、ネットには常に魚がいます。たとえば、クラス定義の前に検出コードを挿入したい場合、対応するアクセサーを書き続ける必要があります。パスを取得し、対応する検出コードをその場所に配布するには、さまざまな構文と対応するアクセサータイプに精通して、スムーズに実行する必要があります。

上記の変換後、最終コードに新しいコードが追加されます(すべての検出コードが挿入されています)が、これにより新しいコードがトリガーされます。この新しいコードを実行すると、上記のすべての検出コードが再度実行されます。これにより、他のモジュールリーダーが壊したくない多くのコード領域が壊れるため、実際には、スイッチを使用して検出コードを配布する必要があります。もちろん、スイッチは実際には次のように非常に単純です。

// 基于 AST 在模块中分发的调试开关
if(require('@tencent/vdebugger').call(this, key)){ debugger }
// 或者这样,虽然好看点,但这样 debugger 在闭包里面拿不到上下文
require('@tencent/vdebugger').call(this, key) || (() => { debugger })()
// 注意这种下面类似这种写法是不行的↓
require('@tencent/vdebugger') || debugger

require('@tencent/vdebugger')パッケージを使用して、グローバル変数やローカルストレージなどの構成を読み取るように設計できる関数を使用して、debugger利便性をデバッグするために注意が必要ないくつかの小さな詳細がある位置を決定するブール値を返すことができます。debuggerこのキーワードはスコープを区切る必要があるためfalse || debuggerrequire('@tencent/vdebugger')このように書くことはできません。また、パッケージ内の構成を読み取った後のこの関数は、eval検出コードを実行する方法になる可能性があるためcall、現在のスコープで代理店を使用でき、より便利です。デバッグに行きます。

もちろん、実際の状況は想像以上に複雑かもしれません。簡単な例を見てみましょう。分散スイッチは、ワーカーにパッケージ化されたコードに挿入される可能性があるため、大規模なプロジェクトではワーカーが頻繁に使用されますが、ワーカーはそれを読み取ることができません。ドキュメント、ウィンドウ、これらのオブジェクトは、ナビゲーター、場所、およびXMLHttpRequestを使用できますが、ローカルストレージの読み取り構成やその他の手段では制御できないため、デバッグスイッチをワーカーコードに配布する必要があるかどうかを検討する必要があります。対応するスイッチおよびその他の問題を伝達する方法。

最も単純で失礼なのは、ワーカーコードをパッケージ化するときにフィルタリングすることです。

!isWorker && new DebuggerPlugin({
    debugConfig: path.resolve(dirName, '../debug.json'),
}),

もちろん、スイッチングエフェクトワーカーを配布する必要がある場合は、読み取り用に構成された通信スイッチの手段を実装する必要があります。最も一般的な通信手段はpostMessageに基づいているためrequire('@tencent/vdebugger')、スイッチモジュールがメインスレッド実行コードの構成を受け入れる機能はワーカー検出コードを実行してブレークポイントを開始するかどうかのコマンドを発行します。

myWorker.postMessage(xx);
myWorker.onmessage = () => {
  console.log('Message received from worker');
}

考え

上記の基本機能を実装した後、引き続き多くのエクスペリエンスを最適化できます。たとえば、webpackプラグインを使用して、ローカルコンパイル中に増分更新を実装することもできます。これは、ローカル構成ファイルを変更して自動的に配布することで実現できます。ブレークポイントとデバッグコード、ロジックは比較的単純で、組み込みの適用サイクルプラグインライブラリchokidarを使用して構成ファイルの変更を監視し、トリガーをコンパイルし、ASTを再取得してブレークポイントコードと一緒にコンパイルされたコードをデバッグします。

const chokidar = require('chokidar');
this.watcher = chokidar.watch(["../src/**/.debug.json"], {
  usePolling: true,
  ignored: this.options.ignored
});

総括する

この点に関するデバッグ関連の記事は多くなく、途中で多くの落とし穴が飛び交っています。チームメンバーのサポートに感謝し、この計画を成功裏に実行してください。より志を同じくする人々がTencentドキュメントチームに参加して一緒に参加することを願っています。探索して旅行し、最後にこの記事があなたにいくつかのインスピレーションを与えることを願っていますか????

おすすめ

転載: blog.csdn.net/Tencent_TEG/article/details/108988820