ヘビ皮テンプレート記述言語

ヘビ皮

これは、テンプレートを愛する蛇カウボーイのフランクです。










こんにちは 私の開発-テキストテンプレート「Snakeskin」用のプログラミング言語についてお話しします。 このプロジェクトは3年以上前で、すべての小児期の病気で、彼は無事に病気になった(そして治った)ので、結果を共有したいと思います。







デモ







プライマリリポジトリ







ドキュメント







Gulp、Grunt、Webpackなどのプラグイン







Gitter-ここでは、興味のある質問をすることができます







ちょっとした歴史



私がYandexで働いていたとき(4年前)、同僚とのコーヒーポイントでの熱い議論の主なトピックの1つはテンプレート開発者でした。既存のソリューションの長所と短所について議論し、一部は独自に開発しました。







部門のメインセクションはTemplateToolkit2でした。これは、特にPerl開発者の間で人気のあるテンプレートエンジンであり、クライアントでは最も単純なMicroTemplate(John Rezig作)が使用されました。 そのときでさえ、XSLTのようなエンジンは積極的に強制されましたが、いくつかの理由(この記事の範囲外であるため)は、私たちに適していないものでした。 ハンドルバー、ダスト、クロージャーテンプレート、そしてもちろんバイクなど、他の人と時々実験を行いました。これらすべてが、プロジェクト内のテンプレートエンジンの動物園全体の存在につながりました。







私のお気に入りはGoogle Closure Templatesでした。テンプレートは文字列を返すだけの関数として位置付けられていたため、プログラマーとしても身近でした。 しかし、些細なフィルターを追加するためにJavaコードを編集する必要があることは非常に腹立たしく、放送速度はそれほど暑くありませんでした(本当に感じました)。







そして、私は自分のクロージャーテンプレートを作りたかった ブラックジャックと売春婦 :当然、JSで記述される必要があり、その結果、Javaを知る必要なく、変更に対してオープンになります。 さらに、私は静的ブロックベースのテンプレート継承モデルが好きで、これをDjango Templatesで調べました(そのため、名前-Pythonへの参照)-既存の継承システムの基礎を形成しました。







私は約3日でプロトタイプをスケッチしました。これは、700行のコードの常連のひどいハードコードでした。 その結果で少し遊んだり、同僚と共有したり、フィードバックを受け取ったり、何らかのフィードバックを受け取ったりして、先に進むことにしました。 このケースのリファクタリング、バグの修正、追加 新しい 機会の。 1週間の開発の後、バージョン2をリリースしました。実際、レギュラーと同じハードコードですが、より安定した機能を備えています。 すでに使用されている可能性があります。







結果にしばらく取り組んで数十のアップデートをリリースした後、私は手をこすり、「物事を正しくする時が来た」と考えてコンピューターに座って、約1か月後に第3バージョンをリリースしました:ES6でハードコードを捨ててコードを書き直しました(当時は普通の翻訳者がいなかったので、私も自分の翻訳者をコピーしました(再び、レギュラーにひどいハードコードを付けました-はい、レギュラーが好きです))、構文解析と多くの新機能を備えたツリーの構築を追加しました。







このバージョンは、安定した強力なもので、実際、ステロイドのクロージャーテンプレートでした。 その結果に満足し、個人のプロジェクトでSnakeskinを使用し始めました。時々、新しいアップデートとパッチをリリースしました。







後でHAMLとJadeに会って、構文へのアプローチが気に入ったため、Snakeskinに似たものを追加することにしました(このソリューションの結果はJadeのような構文になりました)。 数ヶ月にわたる活発な開発の後、私は4番目のバージョンをリリースしました。これは、言語の歴史における真のマイルストーンとなり、さらなる開発を決定しました。 5番目と6番目は4番目のバージョンの修正にすぎませんでしたが、 重大な変更が必要であり、 SemVerのバージョンパターンとしてSemVerを選択したため、メジャーバージョンを混乱させる必要がありました。







私はかなり長い間SS6を使用し、さまざまなプロジェクトで私の友人や同僚もそれを使用し始めました-結果として、しばらくして、苦情のリストが蓄積されました-非常に長くはありませんが、それでも多くの機能があり、それらは非常に混oticとして現れました、およびディレクティブ間の「競合」が表示されるようになりました。 この理由は、初期の言語仕様がないことでした。「ウィッシュリスト」が登場するにつれて開発が進みました。







私はあなたがもうそのように生きることはできないと決めました-あなたはすべてを標準化し、ゴミを取り除く必要があります。 開発は1年半続きました(ただし、最大で6か月間アクティブでした-空き時間の不足が影響を受けました)が、最終的に、私たちはSnakeskin:version 7で最も安定した思慮深いリリースを得ました。 そして私は彼を心から誇りに思っています。







初見



Snakeskinに最も適しているのは、CoffeeScriptやTypeScriptのように、JSに対する単なる「砂糖」であるという定義であるように思えますが、テンプレートを書くというかなり狭い専門性があります。 もちろん、アプリケーション全体をSSで作成することは完全に可能ですが、そうではなく、あまり便利ではありません。 SSは、メイン言語(主にJS)での使用を目的としています。







select.ss







- namespace select - template main(options) < select - forEach options => el < option value = ${el.value} {el.label}
      
      





select.js







 import { select } from 'select.ss'; class Select { constructor(options) { this.template = select.main(options); } } const newSelect = new Select([{value: 1, label: ''}, {value: 2, label: ''}])
      
      





ここで、JSのメインファイルでは、Snakeskinのファイルがモジュールとして接続されています(たとえば、 WebPackのプラグインはこのようなシームレスな統合を提供します)。 それから名前空間select



をインポートし、クラスSelect



を宣言します。 Select



インスタンスを作成するとき、 main



テンプレート( main



テンプレートが変換された)を実行し、その作業の結果をtemplate



プロパティに割り当てますnewSelect



場合は次のようになります。







 <select> <option value="1">  </option> <option value="2">  </option> </select>
      
      





ご覧のとおり、SSはJS(具体的にはES5)に変換されるため、メインコードで非常に使いやすくなります。







Snakeskinを作成し始めた理由について話すと、主な動機はテンプレートコードを変更せずにサーバーとクライアントで同時に使用できる強力なコード再利用機能を備えたテンプレート言語を使用することでした。 そして、もちろん、言語とアイデアの新しい要件が「しかし、私にそのような機能を追加する必要があります」というスタイルで現れ始めました。これらすべてが、創造的かつ論理的に意味のあるもので、スネークスキンをあなたが今見ているようにしました。







たとえば、「時代の要件」の1つは、独自のテンプレート言語(AngularやReactなど)を持つフレームワークやライブラリとのシームレスな統合の必要性でした。そして今ではSnakeskinが非常にうまく機能しています。







SSを使用して角度パターンを作成する例:







 - namespace myApp - template main() < label Name: < input type = text | ng-model = yourName | placeholder = Enter a name here < hr < h1 Hello {{yourName}}!
      
      





作業結果main









 <label> Name: </label> <input type="text" ng-model="yourName" placeholder="Enter a name here"> <hr> <h1> Hello {{yourName}}! </h1>
      
      





Snakeskinはコードの量を大幅に削減し、レイアウト要素の再利用(継承、構成、不純物など)を可能にし、Angularはデータバインディングを実装します。 技術的な観点から、SSはAngularが使用するテンプレートを生成します。







どこで使えますか





 'use strict'; const http = require('http'); const ss = require('snakeskin'); //    //     - const tpls = ss.compileFile('./myTpls.ss'); http.createServer((req, res) => { res.writeHead(200, {'Content-Type': 'text/html'}); //   foo    res.write(tpls.foo('bar', 'bla')); res.end(); }).listen(8888);
      
      





もちろん、実際にはExpressやKoaのようなサーバーフレームワークになりますが、重要ではありません。 また、テンプレートは(そしてできれば) GulpまたはGruntの プラグインを使用して事前に翻訳し、受信したファイルを接続することもできます。または、上記のようにWebPackを使用します。









言語の概要



ここで基本的な概念について説明します。まだ質問がある場合は、 ドキュメントまたはGitterへようこそ。







メイン



パターン



何度も述べたように、Snakeskinテンプレートは翻訳後にJavaScript関数になります。







 - namespace myApp - template main() Hello world!
      
      





放送後、次のようになります。







 if (exports.myApp === 'undefined') { var myApp = exports.myApp = {}; } exports.myApp.main = function main() { return 'Hello world!'; }
      
      





もちろん、これは単純化されたコードですが、全体的には次のようになります。







構文



SSは2種類の構文をサポートしています。









 {namespace myApp} {template main(name = 'world')} Hello {name}! {/template}
      
      





このモードは、コントロールスペースを含むテキストの生成に便利です。たとえば、 Pythonコード マークダウン







:中括弧がよく使用されるテキストを生成するために、SSには特別なメカニズムがあります。









 - namespace myApp - template main(name = 'world') Hello {name}!
      
      





この構文の主な利点は、簡潔さと明快さです。 XMLのような構造の生成に最適です。







SSは混合構文もサポートしています。







 - namespace myApp {template hello(name = 'world')} Hello {name}! {/template} - template main(name) += myApp.hello(name)
      
      





構文とそのタイプについての詳細







コード再利用ツール



継承



SSでは、各テンプレートはクラスです。つまり、メソッドとプロパティがあり、別のテンプレートから継承できます。 子テンプレートは、継承された親メソッドとプロパティをオーバーライドし、新しいものを追加できます。







テンプレートの継承の例







 - namespace myApp ///  sayHello  base ///  SS      , ///     --    , ///     - block base->sayHello(name = 'world') Hello {name}! - template base() - doctype < html < head ///   head ///     , ///        - block head < title ///   `title`,    - title = ' ' ? < body - block body ///   sayHello += self.sayHello() ///    - block child->sayHello() ///   sayHello  - super Hello people! ///    - block child->go() Let's go! ///  child   base - template child() extends myApp.base ///   - title = ' ' ///    - block body - super += self.go()
      
      





child



実行結果







 <!DOCTYPE html> <html> <head> <title> </title> </head> <body> Hello world! Hello people! Let's go! </body> </html>
      
      





テンプレート、入力パラメーター、テンプレートのデコレーター、さまざまな修飾子を継承する場合、 ここで詳細を読むことができます。







構図



Snakeskinのすべてのテンプレートは関数であるため、当然、どのテンプレートも他のテンプレートを呼び出すことができます: call



ディレクティブはこれに役立ちます。







 - namespace myApp - template hello(name = 'world') Hello {name}! - template main(name) - call myApp.hello(name) ///    += myApp.hello(name)
      
      





値としてのパターン



Snakeskinでは、テンプレートをオブジェクトの変数またはプロパティに割り当てたり、関数に引数として渡したりすることができます。







 - namespace myApp - template wrap(content) < .wrapper {content} - template main(name) += myApp.wrap() < .hello Hello world!
      
      





main



結果







 <div class="wrapper"> <div class="hello"> Hello world! </div> </div>
      
      





モジュール



Snakeskinで記述された各ファイルはモジュールです。グローバル変数はその中にカプセル化され、すべてのテンプレートがエクスポートされます。 モジュールは、 include



ディレクティブを使用して他のモジュールをプラグインできます。







したがって、コードを簡単に論理部分に分割し、プラグインライブラリ(および場合によってはフレームワーク)を作成し、一般に「分割して征服する」というルールを容赦なく従うことができます。







math.ss







 - namespace math - template sum(a, b) {a + b}
      
      





app.ss







 - namespace myApp - include './math' - template main() 1 + 2 = += math.sum(1, 2)
      
      





myApp.mainを呼び出した結果







 1 + 2 = 3
      
      





素敵なパン





 - namespace myApp - template main((str|trim), name = ('World'|lower)) - var a = {foo: 'bar'} |json
      
      





SSにはすぐに使用できる便利な組み込みフィルター数多くあります。それらが十分でない場合は、独自のフィルターを 簡単に追加できます。









 - namespace myApp - import { readdirSync } from 'fs' ///    ./foo - template main((str|trim), name = ('World'|lower)) - forEach readdirSync('./foo') => dirname {dirname}
      
      







 - namespace myComponent - template render() < .hello {{ this.name }}
      
      





 import React from 'react'; import { myComponent } from './myComponent.ss'; const Foo = React.createClass({ render: myComponent.render });
      
      





このようなシームレスな統合のために、テンプレートがReactを使用して作成された要素を返す場合、 jsx



フラグをjsx



してjsx



プラグインjsx



ます。









 - namespace myApp - template main() < .hello /// hello__wrap < .&__wrap /// hello__cont < .&__cont
      
      







 - namespace myApp - template main(area) < ${area ? 'textarea' : 'input'}.b-${area ? 'textarea' : 'input'}   
      
      





area



の値に応じて、結果は次のようになります(with area == true



):







 <textarea class="b-textarea">    </textarea>
      
      





または( area == false



):







 <input class="b-input" value="  ">
      
      







 - namespace demo - import Typograf from 'typograf' /// -    JS   SS - template typograf(params) - return () => target - return () => - return new Typograf(params).execute(target.apply(this, arguments)) ///   index     - @typograf({lang: 'ru'}) - template index()  -  !
      
      







 - namespace myApp - async template main(db) - forEach await db.getData() => el {el} - template *foo(data) - for var i = 0; i < data.length; i++ {data.value} - if i % 1e3 === 0 - yield
      
      





非同期操作のディレクティブ 」のセクションも参照してください。









おわりに



Snakeskinがあなたに興味を持ってくださることを心から願っています。試してみて、喜んでそれを使ってください。







この記事の執筆と編集を手伝っくれたtrikadinに心から感謝します。 ちなみに、この男は「 Edil 」のフロントエンドとして機能し、現在はWebのメインテンプレート言語としてSnakeskinをホストしています。 彼は幸せだと言い、以前はSSなしでどのように暮らしていたのか理解していない:)







また、言語開発とサポートに関するアイデアについてjavascript.ruフォーラムチームに感謝します。







プロジェクトのGitHub-eの問題で見つかったバグについて書き、コメントまたはGitterのいずれかに表示される質問をします。いつも喜んで答えて説明します。







頑張って








All Articles