howdylikes

Google Developersってわかりづらいよね

Error: [$compile:multidir] Multiple directives

表題の通り実行時にエラーが出たので原因と解決方法のメモです。

Webpack + AngularJS + TypeScript構成です。

最終的にエラーになる理由はディレクティブを複数定義したからです。 Webpackが生成するbundle.jsが同じのを吐いているんですよね。

// 呼び出し元
import './app/app';
import './header/header';
// 変換後
    __webpack_require__(8);
    __webpack_require__(9);

/***/ },
/* 8 */
/***/ function(module, exports) {

        // 省略
    angular.module('app')
        .component('myApp', new MyAppComponent());

/***/ },
/* 9 */
/***/ function(module, exports) {

        // 省略
    angular.module('app')
        .component('myApp', new MyAppComponent());
/***/ },

Step 1

いろいろためしてみたところtsconfig.jsonをリネームして読まないようにして実行したところすんなり通る。。

ちなみにこんなんです。

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "outDir": ".",
        "out": "dummy.js",
        "rootDir": ".",
        "sourceMap": false
    },
    "files": [
        "./src/ts/app.ts"
    ]
}

Step 2

1つずつ消していったところ

"out": "dummy.js",を消した時にエラーが消えました。

いやーよくわからない(自分がわかってないだけ)ですね。

tsconfingがTypeScriptコンパイラのオプションてのはわかるんですがそこにts-loader経由でWebpackに入れるとなんでこういう動きになるのか。。。

もともとWebpackを使わずにコンパイルしてたのでoutとかが残っちゃってたのですが一旦tsconfigを消しても不都合はなさそうなのでその作戦でいきます。

webpackとts-loaderとが何をしているかちゃんとみないとあかんですね、黒魔術すぎて詰まるとさっぱりわからない。。。

Angular JS require

Angularをrequireしてやったぜと意気揚々としていたのですが
angularという変数がグローバルに作られててなんでや、、、と思ってソースを追っていたら angularが結局グローバルに書き込んでいるみたいですね。。 https://github.com/angular/angular.js/blob/0b1b9112a341f7f798db915575fad63e0e59894e/src/Angular.js#L163

angular           = window.angular || (window.angular = {}),

つまりエントリーポイントの頭でrequireしてやればその他のところでは普通にangular.XXXが使えるということ。。

var myAngular = require('angular');
// つまりrequire('angular');だけでいい

console.log(myAngular); // Object {version: Object, callbacks: Object}
console.log(angular); // Object {version: Object, callbacks: Object}

webpackメモ

全然わかっていないのですが、webpackというのがフロントエンド開発でよさげらしいので試してみたメモ。

基本的な使い方は公式のチュートリアルがわかりやすいのでそちらを参照。

SourceMap

そのまま1つのjsにまとめられたらデバッグができなくて死が見えているのでSourceMapを出力する。

webpack実行時にオプションを追加して実行

実行時に--devtool source-mapを追加するだけ。

webpack --devtool source-map source.js dest.js

Developer toolsのsourceタブにwebpack://というのができています。 ここからブレークポイントを入れることが可能です。簡単ですね。
f:id:howdy39:20160202124228p:plain

configファイルを使う

コマンド打つのもだるいのでconfigファイルを作ります。
リファレンスは以下のページ

http://webpack.github.io/docs/configuration.html

configファイルを作る

touch webpack.config.js
//webpack.config.js
module.exports = {
    entry: './source.js',
    output: {
        filename: 'dest.js'
    },
    devtool: "#source-map",
};

実行

webpack

f:id:howdy39:20160202125831p:plain

wetpack.config.jsというのがデフォルトの設定ファイル名です。
開発用とか切り替えたい場合は別ファイル名で作って--configで指定可能です。

webpack --config webpack.config-dev.js 

gulpからwebpackを呼ぶ

gulp-webpackというのを使います。

github.com

npm install -D gulp-webpack
touch gulpfile.js
//gulpfile.js
var gulp = require('gulp');
var webpack = require('gulp-webpack');
gulp.task('webpack', webpackFunc);

function webpackFunc() {
    gulp.src('source.js')
        .pipe(webpack(require('./webpack.config.js')))
        .pipe(gulp.dest('./'));
}

gulpのsrcに元ファイルを組み込んでいるのでwebpack.config.jsのentryは意味が無い扱いになっているんでしょうかね。ちょっと不透明な部分ですね。

実行

gulp webpack

f:id:howdy39:20160202125710p:plain

gulp 初期構築メモ

gulpでsassを使うまで

前提条件

nodeJSが使えること。

gulp

gulpのインストール

npm i gulp -g
# iはinstallのエイリアス

package.jsonを作る

npm init

sass

sassのファイルを作る

mkdir sass && touch sass/style.scss
/*style.scss*/
h1 {
  color: red;
  &:hover {
    font-size: large;
  }
}

sassのライブラリをインストール

npm i -D gulp-sass
# -Dは--save-devのエイリアス

gulpfile.jsを作る

srcに対象ファイルでpipeで処理を繋げていきます。

touch gulpfile.js
// gulpfile.js
'use strict';
var gulp = require('gulp');
var sass = require('gulp-sass');

gulp.task('sass', sassFunc);

function sassFunc() {
    gulp.src('sass/**/*.scss')
        .pipe(sass())
        .pipe(gulp.dest('./css'));
}

実行

sassというタスク名で登録してあるので以下のように実行

gulp sass

css/style.cssが作成されます

/* style.css */
h1 {
  color: red; }
  h1:hover {
    font-size: large; }

自動化

タスクの追加

このままだと毎回手打ちとかめんどくさいのでファイルの監視を入れます
watchに監視対象ファイル、タスク名を渡すだけですね。これは簡単。

// gulpfile.js
gulp.task('default', defaultFunc);

function defaultFunc() {
    gulp.watch(['sass/**/*.scss'],['sass']);
}

実行

defaultタスクに入れたのでそのまま実行

gulp

あとは自動検知して勝手にsassタスクをおこなってくれます。

IntellijIDEAでTypeScriptのコード補完

TypeScriptの設定

Enable TypeScript CompillerをON

f:id:howdy39:20160125212255p:plain

AngularJSのライブラリをダウンロードしてみる

f:id:howdy39:20160126084844p:plain f:id:howdy39:20160126084521p:plain

この状態でコード補完が微妙に効く

f:id:howdy39:20160126122816p:plain

several definitionってのは 複数定義されているから出せない?ってことなのですが、よくわからない。
ちなみにこのtsを自動トランスパイルしたjsの場合は以下のようにちゃんと引数まで出てきます。

f:id:howdy39:20160126123041p:plain

$scopeを解釈していないため$waではじまるものを適当に引っ張っているみたいです。
なので後述する型定義ファイルの追加$scopeの型指定をすればちゃんとコード補完されます。

この状態だとトランスパイル時にエラー

つまりこのライブラリの追加というのはコード補完時の参照であってトランスパイル時の参照ではないということですかね。

Error:(4, 1) TS2304: Cannot find name 'angular'.

f:id:howdy39:20160127082051p:plain

型定義ファイルの追加

これでトランスパイル時に参照してくれるはず!

tsdというツールを使用

github.com

tsd initで初期化

f:id:howdy39:20160126124650p:plain

tsd query angular -rosa installで定義ファイルをDL

f:id:howdy39:20160126124737p:plain

定義を追加

/// <reference path="./typings/tsd.d.ts" />

エラーが消えました!

f:id:howdy39:20160127082411p:plain

コード補完もちゃんと効きます。

f:id:howdy39:20160127084248p:plain

まとめ

  • JavaScriptのライブラリ追加はコード補完用
  • 定義ファイルの追加をすればトランスパイル時のエラーは消える
  • 定義ファイルの追加をすればJavaScriptのライブラリ追加をしなくてもコード補完される模様(同階層のjsだったり定義ファイルの追加でIDEAが自動で解釈してくれるみたい

IDEAは賢すぎて逆にわかりづらい。
TypeScriptはイイ。

IDEA プラグインの追加(AngularJS)

WebStormではデフォルトであったのですがIDEAにはなかったのでIDEAにAngularJS用のプロジェクトを作るためのメモ
他のプロジェクトも同様でしょうきっと。

f:id:howdy39:20160117211547p:plain

メニューのItetelliJ IDEAから
f:id:howdy39:20160117211558p:plain

f:id:howdy39:20160117211712p:plain

f:id:howdy39:20160117211729p:plain

f:id:howdy39:20160117211814p:plain

f:id:howdy39:20160117211950p:plain

AngularJS プロミス $q

jQueryでいう$.Deferredですかね。

https://code.angularjs.org/1.4.8/docs/api/ng/service/$q

$q.defer()でdeferredオブジェクトを取得してdeferred.promiseを返すようにする。
deferredオブジェクトにはpromiseと3つのメソッドが存在し必要に応じて使用する。

promise: Promise
resolve: (value) // 成功用
reject: (value) // 失敗用
notify: (value) // 通知用

promiseには以下の3つのメソッドが存在する。
通常はthenを使用し失敗時だけハンドリングしたい場合はcatch、結果問わず後処理をしたい場合はfinallyを使用。

then(successCallback, errorCallback, notifyCallback)
catch(errorCallback)
finally(callback, notifyCallback)

$q.all(promises);ですべてのプロミスが解決したあとに解決するプロミスを生成する

e.g.