フロントエンド:開発とサポート(+投票)





あなたが新しいプロジェクトに移されたと想像してみましょう。 または、仕事を変えてプロジェクトについて聞いただけです。 あなたは職場に座って、マネージャーがあなたのところに来て、手を振って...すぐに、プロジェクトページを開き、モニターを指で突いて、「今後のイベントXについてのインフォーマー」を挿入するように要求します。 この上であなたは...何をすべきか? どこから始めますか? 「インフォーマー」を作成するには? 適切なテンプレートはどこにありますか? そして、他の問題の海。



カットの下には、これらのプロセスを整理する方法、SPAを準備するために作成するツールについてのストーリーがあります。 さらに、Live Coding / Hot Reloadの実装の技術的な詳細、およびVirtualDomとReact with Angularについても少し説明します。



始めましょう。 そのため、ここではプロジェクトが残されています。チームリーダーは、リポジトリの場所を教えてから、README.mdを読んで、それで終わりです。



README.md



これはプロジェクトに没頭するときの出発点であり、基本的な情報を提供します:





gitlabインターフェースでの表示例






すべてについてのすべては約5分かかります。 READMEから学ぶ最も重要なこと:必要な問題を解決するには:



  1. NodeJS / npmをインストールします。
  2. プロジェクトリポジトリのクローンを作成します。
  3. npm install



    およびnpm start



    実行します。
  4. ブラウザーでプロジェクトを開き、右下隅の「スポイト」をクリックします。 ;]


しかし、順番に見てみましょう。



設置



私たちは非常に長い間バッチ開発を使用してきました。そのため、多くの部分(うなり声とむしゃくしゃタスク、ユーティリティ、UIコンポーネントなど)が別々のnpmまたはjamパッケージとして開発されています。 このアプローチにより、プロジェクト間で可能な限りコードを再利用でき、バージョン管理(semverによる)が可能になります。さらに、タスク専用の各パッケージのインフラストラクチャを組み立てることができます。 そして最も重要なことは、レガシーではなく、パッケージは独立しており、最終的には優れたオープンソースになる可能性があることを知っています。



さらに、 postinstall



などのnpmフックを使用することを忘れないでください。 これを使用して、次のようなgitフックをインストールします。





最後のフックは奇妙に思えるかもしれませんが、動的に更新される多数のパッケージを操作する場合、それなしには方法がありません。 git pull



と入力すると、開発者は現在のバージョンのプロジェクトを取得する必要があります。これは、 npm install



強制的に実行することによってのみ実現できます。



プロジェクトがnpmまたは別のサードパーティのパッケージマネージャーに依存している場合は、ローカルレジストリを管理して、外部の世界とその問題(左パッド、Roskomnadzorなど)に依存しないようにします。



打ち上げ



npm start



は知っておく必要があるすべてであり、フードの下にあるものは関係ありません:gulp、grunt、webpack ... README.mdにはスタートアップパラメータの説明があることを既に書いています:起動時に、アプリケーションはREADME.mdを読み取り、オプションのリストを解析およびその説明、および不明なオプションまたは文書化されていないオプションを使用すると、エラーがスローされます。 これは、ドキュメントの問題を解決する簡単な方法です。説明なし-オプションなし。



起動例:



 npm start -- --xhr --auth=oauth --build > project-name@0.1.0 start /git/project-name/ > node ./ "--xhr" "--auth=oauth" "--build" - : master (Sun Aug 29 2016 10:28:06 GMT+0300 (MSK)) -   - xhr: true (   `XMLHttpRequest`) - auth: oauth (  `proxy`, `oauth`, `account`) - build: true ( ) -   -    3000 -  : localhost:3000
      
      





最初のステップ



タスクに戻りましょう。 そのため、README.mdが読み込まれ、プロジェクトがインストールされて起動され、「クイックブロック検索」または「スポイトツール」がすべてのアイテムに移動します。



ピペットは、コンポーネントの構造とそのパラメーターを分析するためのツールです。 使用するには、ブラウザを開き、「スポイト」をクリックして、「マネージャーが指を突いた」場所を選択します。



使用例
スポイト

画像



検査官

画像



カーソルの下のブロックの構造を示すインスペクターパネルが下に表示されます。 正しいものを見つけたら、それをクリックします。 これで、ネストされたブロックのチェーン全体を確認し、それらが呼び出されるファイルと行を確認することもできます。



ファイル名をクリックすると、... IDEが開き、カーソルが目的の行に配置されます。 近くに「目」があります。クリックすると、選択したブロックでGUI /ビューアーが開きます。







これで、メインエントリポイントが見つかりました。「インフォーマー」の追加を始めましょう。



UIブロックの作成



ブロックを作成するには2つの方法があります(どちらもREADMEで説明されています)。





コンソールツールは、GUIを使用できない場合に必要です。他の場合はすべて、GUIを使用する方が便利で視覚的です。



GUI



これは、表示用のWebインターフェイスであり、最も重要なことは、プロジェクトのUIブロックを開発することです。 彼にできること:





画像



最初のステップは、プロジェクトにそのような情報提供者がいるかどうかを調べることです。 検索を使用して、同様のブロックを見つけ、「スポイト」を使用してその構造を調べ、「+」を押し、新しいブロックの名前を入力し、「OK」をクリックします。その後、GUIが作成されたブロックのビューを開きます。 再びスポイトツールを使用し、IDEを開いてcss / template / jsを編集します。







それで何が起こったのですか? 「OK」ボタンをクリックすると、GUIは典型的なブロックを持つフォルダーを作成します。これは、アーキテクチャでは少なくとも4つのファイルで構成されています。





これらのファイルを編集すると、ページをリロードせずにすべての変更が適用されます。 これは単なるファッションの楽しみではなく、時間を大幅に節約できます。 ブロックにはロジックを含めることができ、ホットリロードでは現在の状態を失わないようにすることができます。これはF5 / cmd + rで発生します。 テンプレートを編集する場合でも、接続されたブロックは自動的に更新されます。 言い換えれば、GUIはあなたにとってちょっとしたプログラミングです。 ;]







そのため、プロジェクトについてほとんど何も知らなくても、新しいブロックを追加できます。 通常のタスクを完了するために何キロメートルものドキュメントを読む必要はありません。 しかし、これは「キロメートル」が不要であることを意味するものではありません:必要な場合でも-メインのメンテナーなしでプロジェクトの知識と生活を深めるために。 たとえば、APIとビジネスロジックを使用するために、内部JSSDKがあり、そのドキュメントはJSDoc3に基づいて生成されます。



ミニまとめ



プロジェクトのドキュメントとコードベースを正しく学習する必要がありますが、すでに徹底的な没入の段階で、最初は典型的なタスクを実行するシナリオを記述するだけで十分です。 そのような指示は簡単で直感的でなければなりません。 自動化できるすべてを自動化します。 ご覧のとおり、この場合、ブロックを作成するだけではありません。自動化は、プロジェクトのインストール、フック、パッケージの更新などから始まります。プロジェクトに入るのは簡単で楽しいはずです。]



技術部



私は遠くから少し始めます。 2012年の初めに、独自のFestテンプレートエンジンを作成しました。 彼はXMLを、クライアントとサーバーで使用できるjs関数に変換しました。 この関数はパラメーターオブジェクトを受け取り、文字列を返しました:クラシックjsテンプレートエンジン。 兄弟とは異なり、この機能は当時非常に最適化されていたため、以前使用していたCテンプレートエンジンのパフォーマンスを達成した純粋なV8で実行できました。



 [XML -> JSFUNC -> STRING -> DOM]
      
      





この間、Festに基づいて、一度に複数のプロジェクト(メール、クラウドなど)で使用されるブロックの内部ライブラリを開発しました。 つまり、ボタン、入力、フォーム、リストなど、共通しています。 実際、これらはレイアウトとコンポーネントを構造化する最初のステップでした。



Festは文字列のみを返すため、次の2つの方法で状態を更新できます。「すべてを再描画する」または「JSからDOMを点ごとに処理する」という方法です。



もちろん、両方のアプローチを使用する必要があります。すべてを再描画する方が簡単で高速な場合、1つのCSSクラスのみを変更する必要がある場合です。 一般に、文字列を生成するテンプレートエンジンを使用する場合、長所と短所があり、多くの人が考えるように、これは決してパフォーマンスではありません。 主な問題がいくつかあります。





したがって、私たちは先に進み始めましたが、既製のコンポーネントの最小限の書き換えの可能性がありました。



多くの実験がありました。 Angularに非常によく似たデータバインディングを入力しようとしましたが、それとは異なり、Festはまだ文字列を返し、DOMに挿入された後にデータバインディングが課されました。 これにより、元の速度を維持し、V8で作業することができました。 残念ながら、大規模なリストでは、ala- $ダイジェストでアンギュラーと同じ問題が依然としてありましたが、実装は少し速くなりました(タスクの一部として)。



やがて、Reactは市場に参入し、VirtualDomを提供してくれました。 ベンチマークの後、私は少しがっかりしました。基本的な「文字のリスト」は、私たちのものよりも約3倍遅いことが判明しました(そして、これは実装を削減したものです)。 さらに、コードを書き換えたくはありませんでしたが、テンプレートを更新するという原則のみを置き換えました。 しかし、銀の裏地があります:Reactはjsコミュニティ全体に弾みをつけ、まもなくキノコのように、vdomの代替実装が増加し始めました: Incremental DOMmorphdomDekumithrilBobrilなど。



問題は小さいままでした。タスクのベンチマークを実施するには、適切なものを選択し、テンプレートのトランスポーターを作成します。



 [XHTML -> JSFUNC -> VDOM? -> DOM]
      
      





しかし、主な目標は、ブロックの最も快適な開発を得ることでした:





さらに、現在のブロックライブラリへのGUI / Webインターフェイスが既にあります。各プロジェクトが特に苦労することなくGUIを展開できるように、アイデアを統一するだけです。



開発



ライブコーディング



WebpackとBrowserSyncが何であるかは誰もが知っています。 それらについては多くのことが書かれているので、それらに焦点を当てることはしませんが、別の方法を示します。ボックス化されたソリューションがあなたにふさわしくない場合の対処方法です。 車輪の再発明を強くお勧めするつもりはありません。決して、これは単なる低レベルのオプションであり、多くの人が忘れて、同じWebpackを「ねじ込む」ことに多くの時間を費やしています。



その場合、必要なのはnode-watch + socket.ioだけです。 プロジェクトに簡単に統合できる既製の2つのツール。



 const fs = require('fs'); const http = require('http'); const watch = require('node-watch'); const socket = require('socket.io'); cosnt PORT = 1234; const app = http.createServer((req, res) => { res.writeHead(200, {'Content-Type': 'html/text'}); res.end(); }); const io = socket(app); app.listen(PORT, () => { watch('path/to', {recursive: true}, (file) => { fs.readFile(file, (err, content) => { const ext = file.split('.').pop(); io.emit(`file-changed:${ext}`, {file, content}); }); }); });
      
      





 <script src=”//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io”></script> <script> const io = io(location.protocol + '//' + location.host) socket.on('file-changed:html, function (data) { // data.file, data.content }); </script>
      
      





以上で、クライアントで変更を受信できるようになりました。



実際には、すべてがこのように見えますが、上記のリストとの主な違いは、クライアントに戻る際のJSとCSSの前処理です。 はい、正確に。 Webpackとは異なり、開発環境ではギャングは使用されず、ファイルはオンデマンドで変換されます。



ホットブロック更新



フェストに新たな命を吹き込むには、vdomを操作するためのライブラリを選択し、xhtml / xmlのトランスパイラーを作成し、実装の問題を考慮してそれらを解決する必要がありました。



何の問題? たとえば、新しい機能(構築/タグ処理)を追加するには、ライブラリに変更を加えてバージョンを上げる必要がありました。 さらに、テンプレートはサーバー上でのみコンパイルできます。



それで、ごちそうがありました。 ;]



また、xml / xhtmlをJSFUNCに変換しますが、この関数は文字列ではなく、citojsに渡されるJSON(これはvdomを操作するための非常に高速でシンプルなライブラリ)を返し、citojsはvdomを既に構築または更新しています。



さらに、テンプレートはクライアント上で直接コンパイルされるため、テンプレートは「そのまま」与えられ、クライアント上でまずASTに変換され、次に変換規則に従ってJSFUNCに変換されます。



たとえば、 `fn:for`タグを変換するルールは次のようになります
 // <fn:for data="attrs.items" as="key" value="item">...</fn:for> 'fn:for': { scope: true, required: ['data'], expressions: ['data'], prepare: (node, {as, key, data}) => ({ as: attrs.as || '$value', key: attrs.key || '$index', data }), toCode: () => ['EACH($data, @@.children, function (&as, &key) {', '});']); }
      
      





これにより、いくつかの問題を一度に解決できました。





したがって、新しいhtmlがクライアントで受信されると、再びJS関数に変換され、このテンプレートに基づいて作成されたすべてのブロックのレンダラーが呼び出されます。

 socket.on('file-changed:html', (data) => { const updatedFile = data.file; feast.Block.all.some(Block => { if (updatedFile === Block.prototype.template.file) { const template = feast.parse(data.content, updatedFile); Block.setTemplate(template); Block.getInstances().forEach(block => block.render()); return true; } }); });
      
      





CSSの場合、ロジックはほぼ同じです。主な変更点は、CSSモジュールを導入して、main.cssに別れを告げ、コンポーネントコードと共にcssを配信すること、セレクターを交差や難読化の可能性から保護することです。



CSSモジュール



どんなに大きく聞こえても、プロセス自体は非常に単純で、すでに知られていました( たとえば )が、便利なツールがないためあまり一般的ではありません。 postcssとwebpackの出現により、すべてが変わりました。 実装に移る前に、これがReactやAngular2などの他のユーザーにとってどのように機能するかを見てみましょう。



React + Webpack



 import React from 'react'; import styles from './button.css'; export default class Button extends React.Component { render () { return <button className={styles.btn}> <span className={styles.icon}><Icon name={this.props.icon}/></span> <span className={styles.text}>{this.props.value}</span> </button>; } }
      
      





React + webpack + react-css-modules



 import React from 'react'; import CSSModules from 'react-css-modules'; import styles from './button.css'; class Button extends React.Component { render () { return <button styleName='btn'> <span styleName='icon'><Icon name={this.props.icon}/></span> <span styleName='text'>{this.props.value}</span> </button>; } } export default CSSModules(Button, styles); @CSSModules(styles) export default class Button extends React.Component { // ... }
      
      





Angular2



Reactとは異なり、Angularはすぐに使えるモジュール性をサポートしています。 デフォルトでは、すべてのセレクターに一意の属性の形式で特異性が追加されますが、特定の「フラグ」を設定すると、シャドウdomが使用されます。



 @Component({ selector: `my-app`, template: `<div class="app">{{text}}</div>`, styles: [`.app { ... }`] // .app[_ngcontent-mjn-1] { } }); export class App { // … }
      
      





私たちのオプションはその中間です。テンプレートを特別に準備する必要はありません。cssをロードしてブロックの説明に追加するだけです。



 import feast from 'feast'; import template from 'feast-tpl!./button.html'; import styleSheet from 'feast-css!./button.css'; export default feast.Block.extend({ name: 'button', template, styleSheet, });
      
      





さらに、置換クラスだけでなく、本格的なインラインスタイルを備えた実験的なブランチもあります。 これは、弱いデバイス(TVなど)で作業する場合に役立ちます。



実際、ブランチ自体は次のようになります。



 const file = "path/to/file.css"; fetch(file) .then(res => res.text()) .then(cssText => toCSSModule(file, cssText)) .then(updateCSSModuleAndRerenderBlocks) ; function toModule(file, cssText) { const exports = {}; cssText = cssText.replace(R_CSS_SELECTOR, (_, name) => { exports[name] = simpleHash(url + name); return '.' + exports[name]; }); return {file, cssText, exports}; }
      
      





ご覧のとおり、まったく魔法ではありません。すべてが非常にありふれたものです。テキストとしてcssを取得し、すべてのセレクターを見つけます。単純なアルゴリズムを使用してハッシュと見なし、エクスポートオブジェクト[元の名前] => [新しい]に保存します。



最も興味深いのは、JS、彼の何が問題なのでしょうか?



JS /ホットリロード



例を考えてみましょう。 クラスFoo



があるとしましょう:



 class Foo { constructor(value) { this.value = value; } log() { console.log(`Foo: ${this.value}`, this instanceof Foo); } }
      
      





さらにコードのどこかに:



 var foo = new Foo(123); foo.log(); // "Foo: 123", true
      
      





次に、 NewFoo



の実装を更新することにしNewFoo







 class NewFoo { constructor(value) { this.value = value; } log() { console.log(`NewFoo: ${this.value}`, this instanceof NewFoo); } });
      
      





はい、既に作成されたインスタンスは引き続き正常に動作します。



 foo.log(); // "NewFoo: 123", true foo instanceof Foo; // true
      
      





このトリックを実行するために、前処理は必要ありません。十分なJS:



 function replaceClass(OldClass, NewClass) { const newProto = NewClass.prototype; OldClass.prototype.__proto__ = newProto; //    Object.keys(NewClass).forEach(name => { OldClass[name] = NewClass[name]; }); //    Object.getOwnPropertyNames(newProto).forEach(name => { OldClass.prototype[name] = newProto[name]; }); }
      
      





はい、それが機能全体、10行です-JSホットリロードの準備ができました。 ほぼ。 私は特にこの機能をオーバーロードしませんでしたが、本質だけを示しました。 良い方法で、古いメソッドをマークする必要がありますが、古いメソッドは削除されていません。



しかし、問題があります:]



 replaceClass(Foo, class NewFoo { /* ... */}); foo.constructor === Foo; // false (!!!)
      
      





解決するにはいくつかの方法があります。



  1. 引き続きWebpackを使用し、クラス作成を特別なラッパーでラップし、作成されたクラスを返し、更新します。
  2. createClass('MyClassName', {...});



    などのクラスを作成するためにバインディングを適用しますcreateClass('MyClassName', {...});



  3. プロキシを使用することもできますが、ここでは前処理も必要になります


その結果、スキームは次のようになります。



 socket.on('file-changed:js', (data) => { const updatedFile = data.file; new Function('define', data.content)(hotDefine); });
      
      





hotDefine



はすべての魔法を行います:要求されたオブジェクト(たとえば、ごちそう)の代わりに、元のオブジェクトではなく、実装を更新する特別なFeastHotUpdater



返します。



コード分​​析ツール



例で示したように、現時点では、ブラウザーから直接要素を検査できるメインツールはスポイトです。 優れた機能の1つは、IDEで目的のファイルを開くことです。 このために、Roman Dvornov lahmatiy / open-in-editorの素晴らしいライブラリが使用されます:



 const openInEditor = require('open-in-editor'); const editor = openInEditor.configure( {editor: 'phpstorm'}, (err) => console.error('Something went wrong: ' + err) ); editor.open('path/to/file.js:3:10') .catch(err => { console.error('[open-in-editor] Ooops:', err); });
      
      





Romanには、 ReactとBackboneを検査するための同様のコンポーネントもあります。 ;]

Romanのコンポーネントインスペクターの例
画像






React、Ember、Angular、Backboneに精通している人は、React Developer Tools、Ember Inspect、Batarand、Backbone Debuggerなどのソリューションをよく知っています。これらすべて、状況を分析するためのDevTools拡張機能です。



最初は、私の計画ではまさに拡張機能でしたが、Chrome APIの利点はこれに例があり、上記の拡張機能はすべてgithubにあるため、常に実装を確認できます。



しかし、残念ながら、ユーザーに拡張機能を提供することは不可能であり、非常に多くの場合、同僚だけでなくマシンの問題を調査する必要があります。 したがって、これまでのところ、再起動せずにブラウザで最大の情報を取得できるツールに集中しました。 クライアントでテンプレートをコンパイルすることの魅力がすべて明らかになります。2つのアセンブリ(combatとdev)は必要ありません。アセンブリは常に1つであり、デバッグすると、コンポーネントに関するすべての可能なメタ情報が常に取得されます。



他に何?



ロギング



バグは常に発生します-それは問題ではありません。 前に何が起こったのか理解できない場合はトラブル。 そのため、ロギングに多くの注意を払っています。 理想的な状況は、戦闘中にいつでもコンソールを開いて、アクション後に何が起こったかを理解できる場合です。



画像



コードカバレッジ



ほとんどの場合、これは実験にすぎませんが、手動テストの品質を確認するために使用できます。 イスタンブールを取得し、コードを実行してテストマシンにロールし、N秒ごとにログカバレッジをドロップします。 このように簡単な方法で、テスター用のスクリプトが機能性をカバーしているかどうかにかかわらず、スクリプトがどれだけうまく書かれているかを確認できます。



表示例
画像

画像



アプリケーション構造の分析



さらに、アプリケーションが大きくなり、分岐し、その構造が理解できなくなると、 それが最初の試みでした;]



アプリケーションの構造を視覚化する最初の試み
画像



, : , . (, ).



画像

画像



, , ; , . — (, , ).



Timeline



, , , — DevTools Timeline. , (, , . .). , , . timeline ( ).



dev-
画像

画像



おわりに



, , , , , – . , «». , bash- . , , , . , , . , React, Vue, Ember, Angular, – Live Coding, Dev Tools . , React react-storybook .



PS .



All Articles