MaskJS、テンプレートエンジン、または新しいバイクについて話しましょう



ここで、最終的に、彼らは多くの自転車の1つを人々と共有するようになりました(現在は個人的な成果と呼ばれています)。 Habrakat以前は、このソリューションの長所と短所がいくつかありました。
プロから:

欠点の:

トピックが興味深い場合-


エントリー

すぐに文法/スタイルについて謝罪させてください。 私はすべてを非常に簡潔に書きたいと思いますが、同時に、誰もが私を理解し、さらに彼らが私を正しく理解するようにします。 あなたは、文字通りすべてを「理解」するコンパイラではありませんが、それぞれが彼自身の経験、判断、その他のことを持っています。 そして、私は記事を書く経験がほとんどなく、ロシア語で書くことも経験していないので、これは私が想像するようにすべてを書くという希望を根本的に否定します。 しかし、私はしようとします、そしてあなたは誓わない。

ちょっとした歴史

数年前、 String.format('N: #{a} : #{b}',{a:1,b:2})という形式の関数が必要でした。
すぐにそれを使用してhtmlをフォーマットすることに気付きました。ほとんどの場合、私の手は縛られています。 結局、条件、条件付き可視性、およびリストによる書式設定が必要です。 さまざまなテンプレートエンジンを見て、 htmljavascriptの混合に大きな嫌悪感を感じ、それに加えてwith(){}eval/new Function('')も喜ばれませんでした。 「本当に少し必要だ」と思って、自分で書くことにしました。 したがって、2つのタグがlist visibleれ、 #{a==1?1:-1}ような形式が作成されました。 正規表現を使用してタグを検索し、 String.formatを検索する必要がありString.format 。 そして一年半、このエンジンはその目的を完全に果たしました-軽快なものより速く、信頼できるものより信頼性がありました。

「そして、私たちは常に十分ではありません...」

それは残念ですが、私たちはそのように準備されています-まあ、少なくとも私はそうです。 html / javascript messを使用せずに、さらに高速で、さらに拡張性を高めたいと思いました。 その瞬間、私は確かに知っていました。カスタムタグが必要です— プレースホルダーなしで同じhtml構造を10回書かないように(家に挿入した後、そこにレンダリングします) 。 そして今、私が座って追加のタグを処理するためにパーサーを終了するとすぐに、それにもかかわらず、先延ばしになり始めたアートへの欲求が目覚めました-「 書き換えてください。コードを書き換えてください。 CoffeeScript Sass / Less ZenCodingを見たことがありますか?彼らが言う人に書き直してください、そうでなければ、私はあなたを眠らせません-ただ知っています。」 そして、このプレッシャーの下では抵抗できませんでした。 それでも、主な優先事項はエンジンの速度でした-美しいエンジンは必要ありませんが、100馬力です。 開始時の車。

ビジネスに取りかかりましょう:ウッド

独自の構文を使用しているため、ツリーに変換する必要があります。 ノードには、Tag = {tagName:'someTagName', attr: { key:'value'}, nodes:[] } 、Literal = {content:'Some String'} 2つのタイプがあります。 そして、古いテンプレートエンジンを使用して1.5年間、タグまたは属性名のテンプレートデータを置き換えることを決して覚えていないので、テンプレートとアナライザーを簡素化するために、それらにのみデータを挿入できるようにします。 したがって、ノードは次の形式になります{tagName:'name', attr: { key:('value'||function)}, nodes:[] } := {tagName:'name', attr: { key:('value'||function)}, nodes:[] }およびLiteral:= {content:('Some String'||function)}functionは、テンプレートデータを置き換えるfunctionあり、テンプレートデータを必要とする値にのみ使用されます。 そこで、私たちは木を植えました。これまでのところ、複雑なものは何もありません( これ以上複雑になることはありません )。

アナライザー/パーサー

興味深い点:

解析自体は非常に簡単です-40行です。 (属性解析は別の関数として取り出す必要はありませんが、可視性は失われます)
コードの一部
 var current = T; for (; T.index < T.length; T.index++) { var c = T.template.charCodeAt(T.index); switch (c) { case 32: //" " continue; case 39: // "'"   T.index++; var content = T.sliceToChar("'"); //  sliceToChar  indexOf    'escape character' //  . indexOf  ,  ,      charCodeAt/charAt/[] if (~content.indexOf('#{')) content = T.serialize == null ? this.toFunction(content) : { template: content }; current.nodes.push({ content: content }); if (current.__single) { //   ,   ,   ;  div > ul > li > span > 'Some' if (current == null) continue; do (current = current.parent) while (current != null && current.__single != null); } continue; case 62: /* '>' */ current.__single = true; continue; case 123: /* '{' */ continue; case 59: /* ';' */ case 125: /* '}' */ if (current == null) continue; //   ; ,   } -    do(current = current.parent) while (current != null && current.__single != null); continue; } //    -  tag   var start = T.index; do(c = T.template.charCodeAt(++T.index)) while (c !== 32 && c !== 35 && c !== 46 && c !== 59 && c !== 123); /** while !: ' ', # , . , ; , { */ var tag = { tagName: T.template.substring(start, T.index), parent: current }; current.nodes.push(tag); current = tag; this.parseAttributes(T, current); //    ; > {,     T.index--; } 



コンストラクター

ツリーがあるため、ドキュメントに挿入するhtml stringを作成する価値はありません。すぐにdocumentFragment作成する必要がありdocumentFragment (ただし、 function renderHtmlも残してfunction renderHtmlます 。 これにより、解析に費やされた時間が大幅に補正されます。
プロセス自体も簡単です。
コードピース
 function buildDom(node, values, container) { if (container == null) container = document.createDocumentFragment(); if (node instanceof Array) { for (var i = 0, length = node.length; i < length; i++) buildDom(node[i], values, container); return container; } if (CustomTags.all[node.tagName] != null) { var custom = new CustomTags.all[node.tagName](); for (var key in node) custom[key] = node[key]; custom.render(values, container); return container; } if (node.content != null) { //  container.appendChild(document.createTextNode(typeof node.content === 'function' ? node.content(values) : node.content)); return container; } var tag = document.createElement(node.tagName); for (var key in node.attr) { var value = typeof node.attr[key] == 'function' ? node.attr[key](values) : node.attr[key]; if (value) tag.setAttribute(key, value); } if (node.nodes != null) { buildDom(node.nodes, values, tag); } container.appendChild(tag); return container; } 



カスタムコントロール

上記のコードからわかるように、カスタムコントロールがシーンに入ります。 コンストラクターは、登録済みのタグハンドラーを検出すると、ハンドラーオブジェクトを作成し、 attrおよびnodes値のshallow copy 、アセンブリコンテキストをrender関数に渡します。 つまり、コントロールは.render(currentValues, container)関数.render(currentValues, container)を実装する必要があります

toFunction(templateString)

実際、ここで魔法が起こります。 確かに、あなたはそれを魔法と呼ぶことはできませんが、私はそれがとても欲しいです。 実際、ここではデータを挿入するためのパーツを取得しています。

、それらを処理し、テンプレートに貼り付けます。

まあ、すべてが照らされているようです。 より興味深い例では、コントロールを介したバインダーの日付の実装があります。 サンプルページのソースコードも参照してください。



ソースコード



オフトピック:
武器庫にはさらに多くの「大きなもの」があります。たとえば、IncludeJS-Requireに似ていますが、独自の「グッズ」があります。 そのようなことに興味がある場合(本番用のリリースライブラリではありません)、githubに投稿して記事を書きます。

頑張って

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


All Articles