Try .NET Core

.NET Coreを動かした、試した記録を書き残します。

音楽サーバ"Mopidy"のフロントエンドを作る:01 AspCore+TSの環境作り

ここを最後に更新してから、はや2年半が過ぎました。
.Net Coreはそろそろ3.0が出るかというこの頃。初期の粗削りさは影を潜め、環境もドキュメントもリッチになり、とても便利になりました。

個人的に少し時間とモチベーションが出来たので、現在の.Net Core環境をベースに、アプリ作りの過程を記事に起こしてみようと思います。
なお、アプリ自体は既に出来上がっており、こちらに公開しております。

Mopidyとは?

Mopidyとは、Linux上で動く音楽サーバです。
純粋なサーバ実装で、UIはありません。フロントエンドは各自好きなExtensionを入れてね、というスタンスです。
フロントエンドとしては、IrisMopedなどが有名です。

既にIrisみたいなオシャレで出来の良い鉄板プロダクトがあるのに、なぜ新しいものを作ろうと思ったか、というと。

大量に曲を登録したときに、お目当ての曲を探し出すのにひと苦労するんですね。
アルバムやアーティストの名前や綴りを覚えてないと、検索もままならない。

というか、検索するのにキーワードを入力しなきゃいけないのが嫌!
ぽちぽちクリックしたらお目当てが出てくるようにならないの...?

これはMopidy本体の検索APIが貧弱なためで、どのフロントエンドを選んでも同じ問題に行き当たります。
ちょうど時間も出来たことだし、いっちょう作ってみるか、となりました。

何が出来たらOKとする?

まず、作るにあたって要件の整理です。クリアしたい内容としては...

  • Mopidy上のアルバムや曲が探しやすいこと。一番の動機です。
  • Raspberry-Pi上で動作すること。我が家のMopidyサーバがラズパイにあるので。
  • お客さんからAdmin-LTEというフレームワークの話を聞いたので、実際に試す。
  • TSはとっても楽しい!ので、ベース環境はAspCore+TypeScript+Vueにする。

こんなものです。

ベース環境づくり

このあたりのコミットで、おおよその土台を作っています。

Visual Studio 2017でAspCore2.2のプロジェクトを作り、AspCoreのコードを"aspCore"フォルダに全部押し込みました。
今回はSPAのつもりなので、AspCoreのViewはまるっと削除。
HomeController.Indexメソッドはdist/index.dev.htmlを返すようにしてあります。

TSのテスト環境としてmocha, chaiを導入し、TS側モデルで読み込んだlodashが動作してるよね、というところを確認。
f:id:try_dot_net_core:20190731214517j:plain
eslintが大量のダメ出しをしてくれていますが、ひとまずスルーです。

Visual Studioでステップデバッグするために

Visual Studio 2017では、TSをwebpack出力した状態では、エディタにブレイクポイントを置いてもブレイクしてくれません。
今までにも度々試行錯誤を重ねたのですが、残念ながら方法が見つかっていません。

そこで、大変回りくどい方法で「外部ライブラリはwebpackを使用」しつつ「TSはtscが出力したものを使用」することで回避しています。

TSは、AMD形式で単一ファイルとして書き出します。
tsconfig.dev.json:

  -- 一部抜粋 --
"compilerOptions": {
    // モジュール単位の出力形式指定: commonjs, amd, system, umd, es2015
    "module": "amd",

    // 単一ファイル出力時のパスとファイル名
    "outFile": "dist/js/tsout.js"
}

CSSと外部ライブラリは、一旦一つのjsファイルで全て取得し、windowオブジェクトに書き出します。
js/libraries.js:

import '../css/site.css';
import * as es6Promise from 'es6-promise';
import * as _ from 'lodash';
import Axios from 'axios';
import * as Enumerable from 'linq';

window.__globals = {
   'es6-promise': es6Promise,
   'lodash': _,
   'axios': Axios,
   'linq': Enumerable
};

windowオブジェクトに書き出された各ライブラリを、今度はAMDモジュール形式にラップします。
最後に、TSの起動クラスを読み込んでTSロジックを開始しています。
dist/boot.js:

// ダミーのCSS
define('../css/site.css', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = {};
});

// es6-promise
define('es6-promise', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = window.__globals['es6-promise'];
});

// lodash
define('lodash', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var instance = window.__globals['lodash'];
    for (var key in instance) {
        var val = instance[key];
        exports[key] = val;
    }
});

// Axios
define('axios', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = window.__globals['axios'];
});

// linq.js
define('linq', ["exports"], function (exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.default = window.__globals['linq'];
});

// AMD出力されたTSロジックを起動する。
var app = require(['Main']);

htmlでは、AMDモジュールを扱うためにRequire.jsを組み込んだ上で、順次取得・処理していきます。
dist/index.dev.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" />
    <title></title>
</head>
<body>
    <script src="js/require.js"></script> <!-- Require.js -->
    <script src="js/libonly.js"></script> <!--  webpack出力 -->
    <script src="js/tsout.js"></script>  <!-- tsc出力 -->
    <script src="js/boot.js"></script>  <!-- 前述のAMDラップ/起動スクリプト -->
</body>
</html>

こうすると、Visual Studioのエディタ上で設定したブレイクポイントで、TSのロジックが止まってくれます。
f:id:try_dot_net_core:20190731222208j:plain
生成済みの song1 オブジェクトの中身に何が入っているかも、参照出来てますね!

なお、本番ビルドの場合は、TSを含めた全てをwebpackするだけにします。
tsconfig.commonjs.json:

  -- 一部抜粋 --
"compilerOptions": {
    // モジュール単位の出力形式指定: commonjs, amd, system, umd, es2015
    "module": "commonjs"

    // 単一ファイル出力時のパスとファイル名
    //"outFile": "dist/js/tsout.js" //無効化
}

htmlは、バンドルされた1本のjsを読み込みのみです。
dist/index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" />
    <title></title>
</head>
<body>
    <script src="js/bundle.js"></script>
</body>
</html>

なお、この方法は決して推奨されるものではありません。
今のところ、他に良い方法が見つかっていないだけ、なのです。
良い方法があれば、どなたかぜひご教示くださいませ...。