VS2017 の EditorConfig の .NET コーディング規則/名前付け規則が中途半端・・・。
この内容で使っている環境は以下のとおり。 - Visual Studio 2017 15.7.1 - VS Code 1.24.0-insider (windows) - Visual Studio 2017 for Mac 7.5 - JetBrains Rider 2018.1 (mac)
TL;DR
- .NET コーディング規則/名前付け規則が指定できるようになっている
- 規則には重要度を指定できて、違反したときに警告やエラーを出すことができる
- ただ、重要度にエラーを指定している規則に違反しても、ビルドは失敗しない
- Visual Studio Code / JetBrains Rider は .NET コーディング規則/名前付け規則に対応していない
- Visual Studio 2017 for Mac は対応しているけどエラーパッドに表示されない
- つまるところ使えるのは Visual Studio 2017 での開発中だけ
- 規則系は StyleCop Analyzer とかでやった方がよい
EditorConfig での .NET コーディング規則/名前付け規則の指定
Visual Studio 2017 から EditorConfig ファイルに標準で対応してて、さらに dotnet 用とか C# 用とかの .NET コーディング規則/名前付け規則が指定できるようになってます。 詳細は以下を参照。
Visual Studio での EditorConfig の .NET コーディング規則の設定 | Microsoft Docs
EditorConfig ファイルでの .NET の名前付け規則 | Microsoft Docs
Visual Studio のオプションの設定にある、C# と VB のコードスタイルで設定できる内容が、ソリューションとかプロジェクトごとにファイルで指定できるようになっている感じですね。
いままでしっかり見てなかったんですが、規則には適用するしないだけじゃなくって、重要度として違反しているときにワーニングを出すのか、エラーとするのかなどを指定できるようです。 で、ふと思ったのです。重要度でエラーに指定しておいたら、ビルド時にチェックしてくれて、ビルド失敗にできるんじゃないの??と、いうことで試してみました。
試してみた EditorConfig とコード
試してみた EditorConfig は以下。インデントサイズと、フィールドに this をつけて参照していないとエラーになるように設定しています。
root = true # All files [*] indent_style = space # Code files [*.cs] indent_size = 2 dotnet_style_qualification_for_field = true:error
コードは以下の通り。シンプルなもんです。
using System; namespace EditorConfigTest { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } private readonly string temp; public Program() { this.temp = "Foo"; } public string Temp => this.temp; } }
Visual Studio 2017 15.7.1 での動き
アニメ GIF にしてみました。
フォーマットも指定通りに動くし、エディタ上に this がついてないよって波線が出てますし、エラー一覧にも表示されてます。
が、しかし、ビルドするとビルドが通ってしまう。VS上ではエラーになっているのに。ここは本当はビルドエラーになって欲しいところです。
VS Code 1.24.0-insider (windows) での動き
インデントを変更したら、インデントガイドは変更されるのですが、フォーマットしたときのサイズは変わらない。。this が付いてないよって波線もエラーも出ないし、もちろんビルドエラーにはならない。
EditorConfig そのものがどこまで有効かがちょっとわかんないですね。。
Visual Studio 2017 for Mac 7.5での動き
Windows のほうの Visual Studio と同様にフォーマットも指定通りに動いて、 this がないよって破線がでますね。ただ、エラー一覧には出ません。 こちらもやはりビルドは通ります。
JetBrains Rider 2018.1 (mac)
インデントは指定通りに動きますね。しかも細かいところですが、 EditorConfig の変更がファイルを閉じずにでもすぐに適用できたのはこれだけでした。規則は予想はしてましたが適用されませんでした。もちろんビルドも通ります。
ということで、、、
プルリクエストもらったときに自動的にチェックして、却下するとかには使えなさそうですね。ここら辺は Visual Studio のアナライザー使った方が良さそうです。ビルドもちゃんと落ちてくれるんで。Rider も 2018.1 からはアナライザーに対応したみたいです。
webpack3でcore-jsのカスタムビルド
この内容はwebpack3とcore-js 2.5をもとに記述しています。
「webpack3でjQueryとBootstrapをくっつける - k_maruの思うところ2」で書いたように、まだまだjQuery + Bootstrapもおおいけど、少しでも何とかしたいって時に、足かせになるのがやっぱりIE11。。IE10以前がほぼ完全にサポート外になったんで嬉しいのはうれしいんやけど、いざ使ってみると、FetchがなかったりPromiseがなかったり、Symbolなかったり、だからもちろんfor ofとかyieldかけなかったり、いろいろとやっぱ足りないんです。
そうなると、やっぱりShimいれよってなって、有名どころでcore-jsかってことなんですが、素で使うと入りすぎる。IE11と最近のブラウザとの差分だけ入ってほしい。
ってことで、webpackでカスタムビルドするコードを書きました。
npmには既にcore-js-webpack-pluginってのがあるんやけど、動かなくって、GitHub側もあんまり動いてなさそうだったので、まぁ、これくらいのコードやったら、webpack.config.jsのなかに直接書けばいいかと思って、フラットで書くことにしました。
以下で、ファイルの先頭にカスタムビルドした結果が追加されます。
webpack3でjQueryとBootstrapをくっつける
この内容はwebpack3とjQuery3とBootstrap4(Beta)をもとに記述しています。
最近はCSSとかHTMLその他諸々とかを一つのJavaScriptにくっつけてしまおうみたいな、まさにコンパイルといえるようなのが流行ってるみたいですね。そこでよく使われるのがwebpack。
ただね上のはSingle Page Applicationがやっぱり中心で、この進みの早いフロントエンドの技術の中でほんの数年前まで一般的やったのに、今はすでにオールドスタイルって呼べるようなjQueryとBootstrap使って、各ページからそれぞれ別にインクルードしてって形のやつはSoRの世界にはまだまだあるのです。
まぁ、頑張ってBowerでとってきて、gulpでくっつけてやってるってところくらいまでかと。で、「いまなんかwebpackってのが流行ってるらしいぞ。Bowerってもう消えたらしい。gulp? うーん。」みたない情報をもとに、「じゃぁ、jQueryとBootstrapとかくっつけてるのをwebpackにしてみるか!」って考えるわけです。
っていうことで、作りました。jQueryとBootstrapくっつけるってのがピンポイントで見つからなかったのでさらしときます。
webpackでくっつける
以下、webpack.config.jsonとpackage.jsonですが、さきにポイントだけ。
- jsとcssをくっつけるのを別々に定義してます
- Bootstrap4からTooltipはPopper.jsを使うようになったらしいので一緒にくっつけてます
- Bootstrap4からアイコン無くなったみたいなのでFontAwesome使ってます
- jQueryはexpose-loaderでグローバルから見えるようにしてます
- Bootstrapのcssはscssからコンパイルしてます
- 今後アプリケーションでメインの色変えたいとかをBootstrapのvariable.scssの変数を上書き(というか先行定義)して対応できるように
- fontファイルはfile-loaderでファイルとして保存してます(指定しないとくっつけようとされて出力先に出力されない)
- jQueryとBootstrapとPopper.jsとFontAwesomeはnpmからとってます。
grunt-typescript の watch オプションでさらに速く
この内容は grunt-typescript 0.3.4 (typescript 1.0.0) をもとに記述しています。
ちょっとまえに grunt-typescript 0.3.2 を公開して、「TypeScriptのコンパイルは遅いけど、それでもgrunt-typescriptが最速じゃないだろうか?」ってのを書いたんやけど、思うところあってちょっと触ったらさらに早くなった。バージョンが 0.3.2 から 0.3.4 と 0.3.3 が飛んでるのは、デバック用の console 出力が残っていたっていうご愛嬌。
で、なにをしたかっていうと、コンパイルを実際に実行する TypeScriptCompiler ってのが中にあってそいつのインスタンスを毎回生成してたんやけど、それを使いまわすようにした。で、 TypeScriptCompiler には addFile っていうコンパイル対象になるファイルを追加するメソッドがあるんやけど、ちょっと調べたら updateFile ってのと getDocument ってメソッドがあったので、コンパイル実行前に getDocument メソッドすでにファイルが保持されているか調べて、保持されててかつ変更されたファイルやったら updateFile メソッドを使うように変更した。ファイルが保持されてなかったらもちろん addFile メソッドで追加。さらに内部ではファイルそれぞれに対して Document ってクラスのインスタンスが生成されて管理されてるんやけど、そこらへんのもろもろの処理が updateFile ですっ飛ばされる模様。
で、どうなったかは以下。
$ grunt typescript:watch Running "typescript:watch" (typescript) task Watching directory.... c:\Work\Git\typescript\src\compiler >> Changed c:/Work/Git/typescript/src/compiler/tsc.ts 117 files created. js: 117 files, map: 0 files, declaration: 0 files (14493ms) Watching directory.... c:\Work\Git\typescript\src\compiler >> Changed c:/Work/Git/typescript/src/compiler/tsc.ts 1 file created. js: 1 file, map: 0 files, declaration: 0 files (4862ms) Watching directory.... c:\Work\Git\typescript\src\compiler
前の記事では1回目は14036msだったのが2回目は8761msで約4.5秒ほど速くなっていたのが、今回は約10秒速くなってます。いい感じ。
updateFile は最後の引数に TextChangeRange クラスを渡せるようになってて、多分、変更された部分だけを指定できるようにもなってるようやけど、さすがに diff とかとるほうが遅いと思われる(というかそのコードを書くほうがしんどい)ので、却下。エディタでのライブコンパイルとかやったら使えそうです。
前後に動かすタスクも指定できるよ!
ちなみに watch オプションは、コンパイル前後に動かすタスクも指定できるようになってるんで、今まで grunt-contrib-watch などで一連の処理を流していたものも再現できるのいい感じです。
typescript: { base: { src: ['path/to/typescript/files/**/*.ts'], options: { watch: { before: ['beforetasks'], //Set before tasks. eg: clean task after: ['aftertasks'] //Set after tasks. eg: minify task } } } }
TypeScriptのコンパイルは遅いけど、それでもgrunt-typescriptが最速じゃないだろうか?
この内容は grunt-typescript 0.3.2 (typescript 1.0.0) をもとに記述しています。
ついさっき grunt-typescript 0.3.2 をリリースしたわけですが、ちょっとコンパイルを早くしてみました。ってことで typescript 自体のコンパイルの速さを tsc と比較して見ました。
grunt-typescript は以下のように指定してます。
typescript:{ base:{ src: ["src/compiler/tsc.ts"] } }
まずは tsc コマンド。
$ time tsc src/compiler/tsc.ts real 0m15.641s user 0m0.000s sys 0m0.015s
で、じつは私の環境が Windows で Visual Studio 2013 の Update 2 RC が当たってる状態なので、この tsc コマンドは WSH で動いてて、node で動いてないです。なので、node で動くように ntsc ってコマンド作ってそちらでも試してみました。内部的には node コマンドで tsc.js を呼んでるだけです。
$ time ntsc src/compiler/tsc.ts real 0m15.543s user 0m0.030s sys 0m0.030s
0.1秒くらい早そうですが、完全に誤差の範囲ですね。
最後に grunt-typescript 。
$ time grunt typescript:base Running "typescript:base" (typescript) task 117 files created. js: 117 files, map: 0 files, declaration: 0 files (13875ms) Done, without errors. real 0m14.700s user 0m0.015s sys 0m0.060s
grunt-typescript のほうが 1 秒くらい早いです。
なぜ早くなっているか
grunt-typescript は基本的には tsc.ts のコードを参考に作ってて、少し Grunt 用の処理をいれて作ってます。なので tsc を直接実行するのにくらべて若干遅くなるくらいでした。で、今回(実際は0.3.0から)ちょっと内部処理に手を出して、速度アップをはかってみました。
tsc はコンパイルのステップとして各ファイルごとにざっくり書くと Syntax のチェック、 Semantic のチェック、 JS の生成、 d.ts の生成とやっているのですが、この Syntax のチェックと、Semantic のチェックですが、lib.d.ts についても実行しています。ですが、 lib.d.ts って typescript の組み込みみたいなもので、そもそも syntax や semantic にエラーがあったら駄目で、前提としてチェックしなくっても問題ないはずなので、 lib.d.ts を対象から外すようにしました。 lib.d.ts ファイルは結構大きなファイルなので、これを外すことで 1 秒くらい稼げました。
ちなみに、 grunt-typescript 0.3.1 で lib.d.ts に syntax チェックと semantic チェックをを含むようにしたら以下の実行速度でした。
$ time grunt typescript:base Running "typescript:base" (typescript) task 117 files created. js: 117 files, map: 0 files, declaration: 0 files (15373ms) Done, without errors. real 0m16.190s user 0m0.030s sys 0m0.031s
今後 typescript の内部構造が変更されたら動かなくなる可能性もあるので少し怖いところもあるのですが、、、。
というか、 lib.d.ts を ES3 のみとか ES5 のみとか HTML DOM用とかの何種類かに分けてくれたらもっと嬉しいのですが、リクエストでも投げてみようかしら。
さらに早い watch オプション を使った2回目以降のコンパイル
さらに watch オプションでファイル監視しているときの2回目以降のコンパイルは、変更されたファイルのみ読み込みと書き出しをするのでさらに早いです。以下のような感じ。
$ grunt typescript:watch Running "typescript:watch" (typescript) task Watching directory.... c:\Work\Git\typescript\src\compiler >> Changed c:/Work/Git/typescript/src/compiler/tsc.ts 117 files created. js: 117 files, map: 0 files, declaration: 0 files (14036ms) Watching directory.... c:\Work\Git\typescript\src\compiler >> Changed c:/Work/Git/typescript/src/compiler/tsc.ts 1 file created. js: 1 file, map: 0 files, declaration: 0 files (8761ms) Watching directory.... c:\Work\Git\typescript\src\compiler
tsc.tsを試しに変更-保存して結果のところを確認すると、1回目は117ファイルが出力されていますが、2回目は1ファイルのみが出力されていますね。ファイルの変更日付を確認しているので複数ファイル更新されたら、そのファイル数分だけ対象にします。実行速度についても大体6秒ほど早くなってますね。これは対象となっていないファイルは読み込みと書き出しを実施していないのは書いた通りですが、それだけではなく、先に書いたコンパイルのステップの JS の生成も外しているからです。
ただし、単一ファイルで出力する tsc でいうところの --out オプションがついている状態だと lib.d.ts が外れている恩恵だけなのでここまでは速くならないのですが。まぁ、それでも tsc コマンドを叩くよりかはコンパイルが早くなるかと。
と、ここまで書いて Syntax のチェックと、 Semantic のチェックもいらないような気がしてきました。"対象になっていない = 先の結果と同じ" なはずなのでイケそうな気がします。ちょっと確認してみよう。。。
TypeScript で関数にプロパティをはやしたい(その2)
この内容は TypeScript 0.9.1.1 をもとに記述しています。
TypeScript 0.9.1.1 がでましたね。次はやっと1.0かな?
さてさて、前回 は Declaration Merging で関数にプロパティをはやしたのですが、やはりというか予想通りというか対応できないケースが出てきました。
で、頭をひねってみて Interface にキャストしてみました。
interface IGreeting { foo(): string; } function greeting(): string { return "hello"; } var g:IGreeting = <IGreeting>greeting; g.foo = function(): string { return "world"; }
予想通り怒られました。
error TS2012: Cannot convert '() => string' to 'IGreeting':
ってことで、無理やり Any にキャストしてからさらにインターフェイスにキャストすることに。
var g:IGreeting = <IGreeting><any>greeting; g.foo = function(): string { return "world"; }
無事に?いけました。生成された JavaScript は以下。
interface IGreeting{ foo(): string; } function greeting(): string{ return "hello"; } var g:IGreeting = <IGreeting><any>greeting; g.foo = function(): string{ return "world"; }
完全に TypeScript の意味なしですね。。。
TypeScript で関数にプロパティをはやしたい
この内容は TypeScript 0.9.1 をもとに記述しています。
TypeScript の 0.9.1.1 のブランチが切られていて、もうそろそろ登場しそうな今日この頃です。
表題の件、 生 JavaScript なら結構やることなのですが、 TypeScript でやると怒られます。例えば以下のようなコードを書いたとして、
function greeting(): string{ return "hello"; } greeting.name = function(): string{ return "k_maru"; }
コンパイルすると、以下のように怒られます。
error TS2094: The property 'name' does not exist on value of type '() => string'.
さて、以下のようなコードの書き方をさせたいとします。
//キャッシュを設定する Lib.cache("user", { name: "hoge" }); //キャッシュから取得 var user = Lib.cache("user"); //キャッシュの設定 Lib.cache.defaultDefinition({ maxSize: 10, expire: 10000 });
Lib.cache 関数でキャッシュの値の設定と取得をさせてます。で、Lib.cache 関数からはえた defaultDefinition 関数でキャッシュの設定を行ってます。まぁ、こまかい突っ込みは置いといて、よくやるコードですよね。多分。
でもこれ、 TypeScript で普通に書くと以下のような感じになると思うのですが、最初の例の通りに怒られてしまいます。
module Lib{ var items: {[index: string]: any;} = {}; export function cache(name: string, value?: any): any{ if(!value){ if(name in items){ return items[name]; } return; } items[name] = value; } cache.defaultDefinition = function(def: any): void{ //set definition } }
The property 'defaultDefinition' does not exist on value of type '(name: string, value?: any) => any'.
まぁ、はやせないものは仕方がないのですが、 TypeScript 0.9 から Declaration Merging って機能で module を関数にすることができるようになってます。ってことで、早速つかってみます。
module Lib{ var items:{[index: string]: any;} = {}; export function cache(name:string, value?:any):any { if (!value) { if (name in items) { return items[name]; } return; } items[name] = value; } export module cache{ export function defaultDefinition(def:any):void { //set definition } } }
これで無事にコンパイルが通って、思ったようなコードの書かせ方ができるようになりました。すべての場合にこれで対応できるかどうかはなぞっていうか難しい気がしますが、まぁ OK でしょう。以下のような JavaScript がはかれてます。
var Lib; (function (Lib) { var items = {}; function cache(name, value) { if (!value) { if (name in items) { return items[name]; } return; } items[name] = value; } Lib.cache = cache; (function (cache) { function defaultDefinition(def) { //set definition } cache.defaultDefinition = defaultDefinition; })(Lib.cache || (Lib.cache = {})); var cache = Lib.cache; })(Lib || (Lib = {}));
って、ここで終わったら万々歳なんですが、もうちょっと話を進めてみて、いまは cache 関数で設定されている生の値を返してますが、 生の値を保持した CacheItem クラスのインスタンスを返したいとします。これも結構よくやりますよね。多分。
以下のようなコードを書かせたいとします。
//キャッシュを設定すると、 CacheItem が返ってくる var cacheItem = Lib.cache("user", { name: "hoge" }); //キャッシュを取得すると、 CacheItem が返ってくる cacheItem = Lib.cache("user"); //CacheItem には色々プロパティがあって、状態とか条件を確認したり変更したりできる if(!cacheItem.expired){ cacheItem.expirationDateTime = new Date(now + (1000 * 60 * 60)); } //もちろん値も取れる var user = cacheItem.value(); Lib.cache.defaultDefinition({ maxSize: 10, expire: 10000 });
実装としては cache 関数は CacheItem クラスのインスタンスを返すんやけど、シグネチャとしてはインターフェイスである ICacheItem としたい。 CacheItem クラスは外から見えないようにして、 new させたくないってところです。 なぜって、 JavaScript で new するのってなんか気持ち悪いってだけですが。
さらに、 ICacheItem インターフェイスは Lib モジュールの下にいるんじゃなくって、 Lib.cache モジュールの下にいるようにします。 Lib.cache は関数であり同時に名前空間的なモジュールなので、そっちのほうがきれいです。
module Lib{ var items:{[index: string]: cache.ICacheItem;} = {}; // ICacheItem を返す export function cache(name:string, value?:any): cache.ICacheItem { if (!value) { if (name in items) { return items[name]; } return; } // CacheItem を作る var item = new CacheItem(value); items[name] = item; return item; } export module cache{ export function defaultDefinition(def:any):void { //set definition } export interface ICacheItem{ //properties } } // cache モジュールの下に作ると、 cache 関数から見えるようにするためには // export しないとだめやけど、 export すると全体に見えてしまうから // Lib モジュールの下に作る class CacheItem implements cache.ICacheItem { constructor(public value: any){} //properties } }
CacheItem クラスが Lib モジュールの下で、実装しているインターフェイスの ICacheItem が Lib モジュールの下の cache モジュールってので、上の階層が下の階層を見てるってので気持ち悪いですが、まぁ、仕方がないです。
そんなこんなで、TypeScript の時はインターフェイスの考え方をちょっと JavaScript とは変える必要があるかなぁと思ったり思わなかったり。TypeScript だけですべて書ききる、かつ他の JavaScript ライブラリも使わないのであれば、 new が気持ち悪いとかないと思うのですがどうしてもやっぱり混ざっちゃうのですよね。そうした場合にできる限り書かせ方は近いほうがよいと考えてて、合わせようとすると、 TypeScript 側に無理が出てしまうので、どっちに重きを置くかの違いなのですがなかなか悩ましい。
最後はだいぶタイトルから話がそれたような気はしますが、以上。