- 追加された行はこの色です。
- 削除された行はこの色です。
[[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
結論から言うと、これ、windowsだと、うまくいかなかった。yoemanのgenerator-generatorは、動作したから、このモジュールの、jhipsterの読み込みが、新しいJHIPSTERに対応してないっぽい感じ
自分用のモジュールのディレクトリ作る
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!
この、書き込んだ値は、package.jsonというファイルに、出力されている。
そこに対応しているJHIPSTERのバージョンが書いてある。
今使っているのは、JHIPSTER6なんだけど、このツールが対応しているのは、JHIPSTER5
みたい、うごかないかもしれない。
*** 実際に、やってみると、 [#e208bb85]
You don't seem to have a generator with the name “jhipster:modules” installed.
But help is on the way:
と、エラーがでた。
自分の直感としては、
npm linkがwindowsでうまくうごいてないのかな?と思ってみたり
して、しらべてみると、
このjhipsterのモジュールジェネレータの作りは、
yoemanのgenerator-generatorをベースにつくっているのだとおもわれます。
質問が、結構似ていたから。多分そうです。
これで、自分のJHIPSTERで作成したプロジェクトに、ディレクトリを移動して、
npm link generator-jhipster-helloworld
yo jhipster-helloworld
で、確認できるみたい。
*** シンボリックリンクが鬼門 [#yfabbc0b]
下記のサイトを見ると、
npm link
をつかうと、globalフォルダにgeneratorが作成されてしまうので、パーミッションの問題や同名のファイルを作成できない等の問題があります。
https://yosuke-furukawa.hatenablog.com/entry/2013/07/14/131903
なので、こんな感じでやっていらっしゃる方がいて、
$ mkdir helloworld
$ cd helloworld
$ mkdir node_modules
$ ln -s <generator-helloworldのパス> node_modules/.
さらに、毎回これやるのが面倒だということで、下記のツールをつくったとありました。
https://github.com/yosuke-furukawa/yeomania
*** npm linkの順番も関係あるの? [#z885463d]
また、npm linkの順番も、下記のサイトをみると、関係あるとかいてありました。
https://60devs.com/simple-way-to-manage-local-node-module-using-npm-link.html
*** その他関係ありそうなサイトのメモ [#x11c1486]
http://mysticdoll.hatenablog.com/entry/%3Fp%3D264
https://engineering.mixmax.com/blog/troubleshooting-npm-link
** うーん、まだ、エラーだなぁ [#l3da91ca]
events.js:174
throw er; // Unhandled 'error' event
^
TypeError: this.getJhipsterAppConfig is not a function
at module.exports.readConfig (C:\jhip\generator-jhipster-mymod\generators\app\index.js:16:47)
at Object.<anonymous> (C:\jhip\generator-jhipster-mymod\node_modules\generator-jhipster\node_modules\yeoman-generator\lib\index.js:424:27)
at C:\jhip\generator-jhipster-mymod\node_modules\run-async\index.js:25:25
at new Promise (<anonymous>)
at C:\jhip\generator-jhipster-mymod\node_modules\run-async\index.js:24:19
at self.env.runLoop.add.completed (C:\jhip\generator-jhipster-mymod\node_modules\generator-jhipster\node_modules\yeoman-generator\lib\index.js:425:13)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)
Emitted 'error' event at:
このエラーどこで起きているのか、ソースコードをみたら、
generaotrs/app/index.jsにて、
readConfig() {
this.jhipsterAppConfig = this.getJhipsterAppConfig();
if (!this.jhipsterAppConfig) {
this.error('Can\'t read .yo-rc.json');
}
},
と、なっているが、これが、どこからも設定されてないが、ソースコード上に、
getJhipsterAppConfig()
がなくてはならないつくりなのに、ない。
** 失われたgetJhipsterAppConfig()を実装するつもりでイメージをつかむ。 [#g8259977]
実装のイメージはこんな感じだろう。
function getJhipsterAppConfig(yo-rc_json) {
// yo-rc_json:yo-rc.jsonをパースしたオブジェクト
var ret = {};
ret.jhipsterVersion = 6; //JHipsterのバージョン
ret.baseName = jdl.baseName;
ret.packageName = jdl.packageName ;
ret.packageFolder = jdl.packageFolder;
ret.clientFramework = jdl.clientFramework;
ret.clientPackageManager = jdl.clientPackageManager;
ret.buildTool = jdl.buildTool;
return ret;
}
ソースコードを解析すると、
node_modules\generator-jhipster\cli\import-jdl.jsに、次のコードを見つけた
getConfig() {
if (jhiCore.FileUtils.doesFileExist('.yo-rc.json')) {
logger.info('Found .yo-rc.json on path. This is an existing app');
const configuration = jhipsterUtils.getAllJhipsterConfig(null, true);
if (_.isUndefined(this.options.interactive)) {
logger.debug('Setting interactive true for existing apps');
this.options.interactive = true;
}
this.applicationType = configuration.applicationType;
this.baseName = configuration.baseName;
this.databaseType = configuration.databaseType || jhipsterUtils.getDBTypeFromDBValue(this.options.db);
this.prodDatabaseType = configuration.prodDatabaseType || this.options.db;
this.devDatabaseType = configuration.devDatabaseType || this.options.db;
this.skipClient = configuration.skipClient;
this.clientFramework = configuration.clientFramework;
this.clientFramework = this.clientFramework || 'angularX';
this.clientPackageManager = configuration.clientPackageManager;
if (!this.clientPackageManager) {
if (this.useNpm) {
this.clientPackageManager = 'npm';
} else {
this.clientPackageManager = 'yarn';
}
}
}
}
* もし、修正したいひな形が下記のいずれかの場合 [#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]
jdl_importer.jsにて
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