[[JHIPSTER一覧]] *** 目次 [#s90f7b0e] #contents * はじめに [#a6f92448] jhipsterでなにが、うれしいかというと、 クラス図をJDLという簡易な設定さえ書けば、 一応動作する画面が、できるということだと思う。 リレーションとかも、考慮済みのテンプレートが、いい感じに、頑張ってくれる。 でも、すでに、自分のプロジェクトのひな型ができている場合がある。 成果物として、望まれているのは、自分のプロジェクトの形式に沿ったコード だったりする。 そうなるとひな形のほうを修正したい。 * もし、ページを追加したい場合 [#xd2fe6b8] 例として、おみくじページの追加例がある。モジュールと呼んでいるらしい。 https://www.jhipster.tech/modules/creating-a-module/ ** モジュール作成のジェネレータもある [#xacd1c6d] https://github.com/jhipster/generator-jhipster-module *** ためしに使ってみる [#wce535e0] 自分がデザインとかしたページのテンプレがつくれたらいいな。 インストールする npm install -g generator-jhipster-module 自分用のモジュールのディレクトリ作る mkdir generator-jhipster-mymod cd generator-jhipster-mymod git使うので、gitのリポジトリとするための初期化 git init yoコマンドで、作るための質問に答える。(Windowsの場合、実行は、PowerShell) yo jhipster-module 質問はこんな感じでした。 ? What is the base name of your module? (helloworld) そのまま、エンター、モジュール名だろうか。 ? Give a description of your module モジュールの説明だろうか description-hello と、回答 ? Do you want to enable hooks for your module from JHipster generator? (Use arrow keys) > No, This is a stand alone module Yes, Enable post entity hook とりあえずNoが、えらばれていたので、そのままEnter ? What is your GitHub username? GitHub使うこと前提?とりあえず、入力 ? Who are you? Firstname Lastname (Firstname Lastname) 氏名を入力 ? Your email? メアドを入力 ? Your home page url? ホームページを入力 > No license Apache License 2.0 GNU General Public License v3.0 MIT License そのまま、NoLicenseにした *** 結果 [#s7a66243] create package.json create .editorconfig create .eslintignore create .eslintrc.json create .gitattributes create .gitignore create .travis.yml create README.md create test\test-app.js create generators\app\index.js create generators\app\templates\dummy.txt ##### USAGE ##### To begin to work: - launch: npm install or yarn install - link: npm link or yarn link - test your module in a JHipster project: - go into your JHipster project - link to your module: npm link generator-jhipster-helloworld or yarn link generator-jhipster-helloworld - launch your module: yo jhipster-helloworld - then, come back here, and begin to code! と表示された。 これで、自分のJHIPSTERで作成したプロジェクトに、ディレクトリを移動して、 npm link generator-jhipster-helloworld yo jhipster-helloworld で、確認できるみたい。 実際に、やってみると、 You don't seem to have a generator with the name “jhipster:modules” installed. But help is on the way: と、エラーがでた。 わからん。 * もし、修正したいひな形が下記のいずれかの場合 [#je94886e] JHIPSTERのサブモジュール拡張機能の通称:ブループリント を使ったほうがいいかもしれない。 https://www.jhipster.tech/modules/creating-a-blueprint/ ** ブループリントで拡張可能な、箇所 [#xdb73b75] - common - client - server - entity - entity-client - entity-server - entity-i18n - languages - spring-controller - spring-service *** ブループリントのジェネレータ [#b2d425ae] ジェネレータもある、質問に答えていけば、ブループリントのジェネレータが生成されるとある。 https://github.com/jhipster/generator-jhipster-blueprint * JDLからのパラメータ [#y48f88a2] 以下は、JHIPSTERのソースコードを読んで、直接改造、するための解析 JDLからのパラメータは、どのように拾えるのか。 ** テンプレートファイル [#scf5c772] テンプレートは、yoというツールを使っていると思う。 JDLをyoで使える形式に変換するのが、JHIPSTERだとすると、テンプレートから、変数を引っ張ってくればいいんじゃなかろうか。 generator-jhipster-vuejs は、node_modulesフォルダに、格納されている さらにその配下の、generatorフォルダを、今回解析してみようと思う。 node_moduleフォルダにある、generatorフォルダが怪しいと思っておこう。 どうやら、jhipsterのコマンドは、 アプリフォルダ/node_moduless/generator-jhipster/cli の commands.js ファイルに記載があった。 ただし、ここから、どこかに行くというわけでは、ない。 モジュール側が、リスナーしているようだ。 例えば、import-jdl というコマンドの場合、次のファイルが、このコマンドを待ち構えるモジュールとして作られている。 import-jdl.js たぶん、次の文が、リスナーの実装っぽい。確認してないけど。。。GREPしたらここしか、import-jdlの文言含んでなかった。 const statistics = require('../generators/statistics'); statistics.sendSubGenEvent('generator', 'import-jdl'); * テンプレートのソースコードを読んで、テンプレートの書き方を逆引きにしてみる [#s8b46879] yoのテンプレートの書き方だけでは、役不足だ。JDL言語で簡易に書いた、 クラス図の情報が、どのように、テンプレートの引数にわたってくるのかが、知りたいよね? だから、テンプレートファイルをみて、解析してみるとする。 とりあえず、エンティティまわりをみてみたいので、下記のファイルを観察してみるとする。 EntityRepository.java.ejs ** コメントの書き方 [#w45c20e8] <%# -%> ** パッケージ名 [#v8c39201] package <%=packageName%>.repository; ** エンティティクラス名 [#u1109869] テーブル名とかに使用 <%=asEntity(entityClass)%> または <%=entityClass%> ** 多対多の場合 [#vf1b04ac] <%_ if (fieldsContainOwnerManyToMany) { _%> <%_ } _%> ** データベースの種類がsqlの場合 [#m61c4897] <%_ if (databaseType === 'mongodb') { _%> <%_ } _%> ** SQLのselect句 [#a296506c] select <%= entityInstance %> ** リレーションしてる文だけ必要なループ [#l1799878] <% for (idx in relationships) { if (relationships[idx].relationshipType === 'many-to-many' && relationships[idx].ownerSide === true) { %> left join fetch <%=entityInstance%>.<%=relationships[idx].relationshipFieldNamePlural%><%} }%>", ** リレーションのjavadocが定義されているかどうかチェック [#ra656797] for (idx in relationships) { if (typeof relationships[idx].javadoc != 'undefined') { } } ** フィールドでループ [#d9159b14] <% for (idx in fields) { } _%> ** フィールドのjavadocが定義されているかどうかチェック [#yfb1dc1d] for (idx in fields) { if (typeof fields[idx].javadoc != 'undefined') { } } ** 未確認の引数 [#v805dd3d] fieldsContainBlob importJsonIgnoreProperties importApiModelProperty importJsonIgnore fieldsContainUUID prodDatabaseType hasTextBlob validation searchEngine fieldsContainBigDecimal * jhipster entityとしたときのメッセージの場所 [#z81bf26d] たまたま、みつけたから、メモしておこう。 node_modules\generator-jhipster\generators\entity\prompts.js にある。 * JDLのパーサー [#n21d66b4] jhiCore.JDLImporter のimportメソッド でパースされているようです。 JDLで、認識できる文法を、自分のプロジェクトに合わせれたら、いいんじゃないだろうか? たとえば、案件ごとに、定義書は、大体似ては、いるが、若干、違う。 それをJDLに、まとめさせて、テンプレートに流すことができたら、すごくよさそうだ。 ** JDLImporter [#zc4db629] node_modules\jhipster-core\lib\jdl\jdl_importer.jsにて import() { const parsedJDLContent = parseFiles(this.files); って書いてありました。 function parseFiles(files) { return JDLReader.parseFromFiles(files); } JDLReaderを調べる必要がありそうです。 const JDLReader = require('../reader/jdl_reader'); とあるので、 アプリのフォルダ/node_modules/jhipster-core/lib/reader/jdl_reader を調べる必要がありそうです。 ちょと、ここで、発見したのですが、removeInternalJDLComments というメソッドで、 JavaDocコメントをパース前に、削除して、台無しにしてしまっているようにみえます。 だれか、修正してくれないかぁ。まあ、いいや、解析を進めよう。 const parser = require('../dsl/api'); とあるので、 /node_modules/jhipster-core/jhipster-core/lib/dsl/api.js を見てみると、 まず、JDL言語の字句解析は、 const lexResult = JDLLexer.tokenize(input); でおこなっており、 パーサーは、 const parserSingleton = JDLParser.getParser(); でパーサーの配列で取得できるようになっており、 デフォルトでは、progが使われているのが分かった。 で、パースをうごかしているのは、 buildAst(cst); で、行っているようだ。汎用的につくってあるので、buildAstは、解析しないことにする。 ** lexer.js [#u609c145] 自分用にコマンドを追加するには、 アプリフォルダ/node_modules/jhipster-core\lib/dsl/lexer.js に、下記のような感じで、認識させる単語を登録させる、必要があるだろう。 たとえば、認識させたい単語が required ならば、それをパーサーに伝える際には、 'REQUIRED' として、伝えたいならば、 createToken({ name: 'REQUIRED', pattern: 'required' }); と書かなくてはならない。 この 'REQUIRED'としたものは、node_modules\jhipster-core\lib\dsl\jdl_parser.jsにて const LexerTokens = require('./lexer').tokens; としており、次のように文法のルール定義に記載されている。 { ALT: () => this.CONSUME(LexerTokens.REQUIRED) }, *** 実務でありそうな要件を考えてみる。 [#i39ea552] 項目の定義がエクセルで、設計済みで、JDLでは、不足しているカラムがあるんだよなー。ってのが、一番ありそうなんじゃ、ないだろうか? となると、やりたいことは、 カラムと、その中身の定義を、字句解析と、構文解析に追加する、方法が分かりたいということだ。 近そうな記述は、最大値の認識させ方かなと思って、解析を進めてみる。 ** maxlengthに着目して、解析をする [#a59174a3] *** lexer.jsでのmaxlength [#h19f6f0f] createToken({ name: 'MAXLENGTH', pattern: 'maxlength', categories: [tokens.MIN_MAX_KEYWORD] }); *** jdl_parser.jsでの、MIN_MAX_KEYWORD [#j620febe] minMaxValidation() { this.RULE('minMaxValidation', () => { // Note that "MIN_MAX_KEYWORD" is an abstract token and could match 6 different concrete token types this.CONSUME(LexerTokens.MIN_MAX_KEYWORD); this.CONSUME(LexerTokens.LPAREN); this.OR([{ ALT: () => this.CONSUME(LexerTokens.INTEGER) }, { ALT: () => this.CONSUME(LexerTokens.NAME) }]); this.CONSUME(LexerTokens.RPAREN); }); } *** jdl_parser.jsでの、minMaxValidation [#nc8cd3d4] validation() { this.RULE('validation', () => { this.OR([ { ALT: () => this.CONSUME(LexerTokens.REQUIRED) }, { ALT: () => this.CONSUME(LexerTokens.UNIQUE) }, { ALT: () => this.SUBRULE(this.minMaxValidation) }, { ALT: () => this.SUBRULE(this.pattern) } ]); }); } 以下、同様に解析していくと、呼び出しているメソッドが、次のようになっていました。 ↑ fieldDeclaration ↑ entityBody *** テンプレートfield_validators.ejsでのmaxlengthの使われ方 [#nfc071c7] if (rules.includes('maxlength') && !rules.includes('minlength')) { validators.push('@Size(max = ' + field.fieldValidateRulesMaxlength + ')'); } どこで、fieldValidateRulesMaxlength 入れてるの? 自動かなぁ どうも、テンプレートを見る限りでは、 field.fieldValidateRules, 'maxlength' が定義されているなら、 field.fieldValidateRulesMaxlength は、あたかも定義済みだよね~という記述だ。 * テンプレートは、どのように管理されているのか? [#k9582859] JHIPSTERは、yoを使っているので、yoでは、どのように、ファイルが生成されているのか、 知りたいところである。 ** yo での一番基本的な、プロンプト入力 からの、テンプレートを使った生成の書き方。 [#w26b2772] class extends Generator { async prompting() { this.answers = await this.prompt([{ type : 'input', name : 'title', message : 'Your project title', }]); } writing() { this.fs.copyTpl( this.templatePath('index.html'), this.destinationPath('public/index.html'), { title: this.answers.title } // user answer `title` used ); } } templatePathに、着目してソースファイルをみると、writeFilesToDiskというメソッドで、ファイル生成していることがわかった。 ** 例えば自作のjavaのテンプレート追加するにはどうするか [#o18c7453] javaのコードのテンプレートと、リネームの設定は、次のフォルダに格納されているみたいだ。 generator-jhipster\generators\entity-server このフォルダのindex.jsが、files.jsを参照するような形になっていて、このフォルダのtemplateフォルダに、パラメータを渡すように、設定していることがわかったので、 もし、自分でテンプレートを追加したいのであれば、 - templateフォルダに、テンプレートを入れる。 - files.jsを修正する で、いけそうだ。 *** 参考ページ [#q3e169f5] https://github.com/SAP/chevrotain/blob/master/examples/lexer/keywords_vs_identifiers/keywords_vs_identifiers.js ** パーサー作成ツールキット chevrotain [#a4164c2b] で、パーサどう書けばいいの?ってなるが、JDLで使っているパーサは、chevrotain を使っているようだ。 *** chevrotain のソース [#h48a278b] https://github.com/SAP/chevrotain *** chevrotain のチュートリアル [#f220fc2b] https://sap.github.io/chevrotain/docs/tutorial/step0_introduction.html *** 参考:いろいろなパーサ生成ライブラリの紹介ドキュメント [#a4506e5c] https://tomassetti.me/parsing-in-javascript/#chevrotain