Rails + webpacker + Vue.js + TypeScript で `export 'default' (imported as 'mod') was not found`

Railswebpacker gem を使って、Vue.js with TypeScript な環境を作った中で長くハマった部分があったのでご紹介したい。

ハマったところ

webpacker で vue と typescript を初期設定して、Vue.js の SFC (Single File Component) で開発していた。
webpacker でコンパイルするときに、export 'default' (imported as 'mod') was not found というエラーが起きた。

結論としては、 vue-loader が ts-loader を使いたいが、ts-loader 側で .vue ファイルを受け入れる設定になっていなかった(みたいな感じ)。

初期設定の再現

$ gem install webpacker
$ rake webpacker:install
$ rake webpacker:install:typescript
$ rake webpacker:install:vue

解決法

webpacker が生成してくれる config/webpack/loaders/typescript.js を以下のように変更する。

Before

const PnpWebpackPlugin = require('pnp-webpack-plugin')

module.exports = {
  test: /\.(ts|tsx)?(\.erb)?$/,
  use: [
    {
      loader: 'ts-loader',
      options: PnpWebpackPlugin.tsLoaderOptions()
    }
  ]
}

After

const PnpWebpackPlugin = require('pnp-webpack-plugin')

module.exports = {
  test: /\.(ts|tsx)?(\.erb)?$/,
  use: [
    {
      loader: 'ts-loader',
      options: PnpWebpackPlugin.tsLoaderOptions({
        appendTsSuffixTo: [/\.vue$/]
      })
    }
  ]
}

これを調べるのに vue-loader のソースを結構読んだ。

vue-loader は Vue.js の SFC 単一ファイルコンポーネントコンパイルするために他のコンパイラ(xx-loader)へ橋渡しする役目を担ってるのかなとわかった。

SFC では HTML を書く template と JSで実装する scriptCSS を書く style と3つの領域それぞれで他の言語を使うことができる。

たとえば、template で pug を使ったり、
script で TypeScript を使ったり、
style で SCSS や SASS を使うことができる。

<template lang="pug">
</template>
<script lang="ts">
</script>
<style lang="scss">
</style>

それぞれコンパイラが違うのでそれを vue-loader さんは

  • "template は pug-loader!!"
  • "script は ts-loader!!"
  • "style は scss-loader!!"

と振り分けてくれている。

実は vue-loader のドキュメント Vue Component の仕様 を熟読するとそういうことが書いてある。

Vue Component の仕様 · vue-loader


参考資料:

FirebaseでTwitterアカウント認証出来た

FirebaseでTwitterアカウント認証出来た

Firebase Authentication で Twitter アカウントを使った認証を作れた。
まだ認証しか出来ないけど、そこまでにハマった点を残しておこうと思う。

用意するもの:

  • Twitter Developer Apps 登録
  • Firebase Project

Twitter Developer Apps

developer.twitter.com

随分前に取得済みだったので、忘れたが、取得はそんなに難しくなかった。

たぶんちゃんと英語を読んで、自分なりに考えたアプリの説明なりを書いていけば承認される。

Firebase Project

firebase.google.com

こちらはすっごく簡単につくれる。 たくさん参考記事はネット上にあるので、検索されたし。

firebase-toolsを使って firebase init とかで実装開始できる。

難しかったところ

Firebase UI の導入

この記事を見ながらやってた。

qiita.com

Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).

自分でアレンジして組み込んでいったら、firebase.initializeApp() が何度も呼ばれて、Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app). というエラーが発生した。

ちょっと検索したら、この記事があったので、リンク先のやり方で解決。

ReactとFirebaseの構成でチャットシステムを目指した · PengNote - 勉強した事や行った場所の感想を書くブログ

Firebase App named '[DEFAULT]' already exists (app/duplicate-app) · Issue #1999 · zeit/next.js · GitHub

f:id:clash_m45:20190908142416p:plain

この問題が解決したらこんなボタンが出た😊 f:id:clash_m45:20190908142433p:plain

Desktop applications only support the oauth_callback value 'oob'

次に遭遇したのはこのエラー。

Twitter API のほうでエラーになっている感じがしたのと、どうもデスクトップアプリと誤認してるようだった。

検索してみると、この記事に行き着いた。

TwitterのOAuthでCallbackさせるときの注意点

Twitter Developer のページへ行って、Apps から自分のアプリの設定詳細ページを開く。

私の場合は、

  • Sign in with TwitterDisabled だった。
  • Callback URLが 空だった。

なので、以下を変更した。

  • Sign in with TwitterEnabled
  • Callback URLが に以下を追加
    • https://${firebaseのドメイン}.firebaseapp.com
    • https://${firebaseのドメイン}.firebaseapp.com/__/auth/handler

callback urlは実際にはコールバックさせたいアドレスにしたら良い。(今回はお試しでトップページに返すだけにした)
2つ目はfirebase uiが使うのかな?1つ目を追加したらエラーメッセージに現れたので追加した。

出来た!

そんなこんなして、ハマりつつも Twitter アカウントで認証できました!

f:id:clash_m45:20190908142444p:plain

Firebase Functions ローカルで関数を実行する時に失敗する件について

Firebase Functionsは firebase-tools を使って、ローカルで関数を実行してテストできる。

結論

firebase functions:shell を使おう!

使い方はこちら

firebase.google.com

例えば、 myFunc という関数を定義したら、

firebase > myFunc.get()

とかで呼べるし、express で myFunc配下に関数 ( /greet ) がある場合には、

firebase > myFunc.get('/greet')

で呼べる。

続きを読む

vueで開いたり閉じたりするコンポーネントを作る

最近vanillaのjavascriptで開いたり閉じたりするUIを作ってて、すっげぇ苦労したので、 「くっ…!!(vueなら簡単に書けるのに…!!)」という気持ちを発散するために、実際に作ってみた。

仕様

開くボタン

  • 「開く」ボタンをクリックするとフォームが開く。
    • 開いている間は開くボタンは消える。

閉じるボタン

  • フォームには「閉じる」ボタンがあり、押すとフォームが閉じる。
    • 閉じている間は閉じるボタンは消える。

作ったもの

See the Pen Vue expand form by clash_m45 (@clash_m45) on CodePen.

コード

styleをはしょってるので、全文はcodepenのほう見てください。

単純に v-if を使って、プロパティ expand がtrueのとき開く、としているだけ。 buttonのon-clickで openメソッド、closeメソッドを呼んで、 expandを書き換えると、vueの力でbindingされて、動的にUIが変化する!

素晴らしい!

<template>
  <div>
    <h3>開いたり閉じたりする入力フォーム</h3>
    <hr>

    <button v-if="!expand" @click="open">開く</button>
    <div v-if="expand">

      <label for="hamburger">Do you like hamburger?</label>
      <input type="checkbox" name="hamburger" id="hamburger">
      <span>checked</span>
      <br>

      <button @click="close">閉じる</button>
    </div>
  </div>
</template>

<script>
  export default {
    name: "count",
    data () {
      return {
        expand: false
      }
    },
    methods: {
      open: function() {
        this.expand = true
      },
      close: function() {
        this.expand = false
      }
    }
  }
</script>

Spring BootとVue.jsを使ったシステムを作ってみた

前にこの記事を読んで、vue.jsを入門してた。

GUIアプリケーションアーキテクチャ総合!みたいなやつ書いてる - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く

実際にギョームで採用したいなーと思ったけど サーバサイドとの結合がよく分からなかったので、簡単なシステムを作ってみた。

github.com

vue.jsで作ったページのボタンをクリックすると、 Spring Bootで作ったHello World WebAPIを呼び出して、 値を画面に表示するっていう簡単なシステム。

出来る限り簡素な作りにしようと思って作った。 READMEに実装の手順を書いてみたので、参考になれば嬉しい。


フロントエンドはvscodeで実装して、 バックエンド(Java)はeclipseで実装するのが一番ラクだった。

ビルド周り凝り始めるとわかりづらくなるので書いてない。

vue.jsはわかり始めるとかなり書きやすいし、 DDD-like Layered Architectureに沿った作りにすると見通しも良くなりそう。

ギョームで使うにはまだ足りない機能が多い

  • バリデーション
  • ユーザ認証
  • Flux的なやつ(vue.js storeでもよさそう)

今日やったリファクタリング - 2 : Enumの値と比較

今日じゃないけど、最近やったリファクタリング

言語はC#.

enumって列挙型で、定数とかを定義するのに使います。
簡単な例で言うと、曜日とかを列挙したり。

public enum Days {
    Sat=1,
    Sun,
    Mon,
    Tue,
    Wed,
    Thu,
    Fri
}

ただ、このままだと結構使いづらくて、こんなコードを書いてしまう場合もあります。

Before

// todayが火曜であることを確認する処理
string today = "4";//どこかから取得した値
bool b = ((int)Days.Tue).ToString().Equals(today);

// 日曜かどうかboolを取りたい場合
bool b = ((int)Days.Sun).ToString().Equals(today);

ひたすらintにcastしてToString().Equals()…!

ダサいので、やめましょう。

After

今回リファクタリングした結果はこんな感じ。

public class Days {
    
    private enum _Days {
        Sat=1,
        Sun,
        Mon,
        Tue,
        Wed,
        Thu,
        Fri
    }

    private _Days val;
    private Days(_Days d) {
        this.val = d;
    }

    public static readonly Days Saturday = new Days(_Days.Sat);
    public static readonly Days Sunday = new Days(_Days.Sun);
    public static readonly Days Monday = new Days(_Days.Mon);
    public static readonly Days Tuesday = new Days(_Days.Tue);
    public static readonly Days Wednesday = new Days(_Days.Wed);
    public static readonly Days Thursday = new Days(_Days.Thu);

    public bool Equals(string value) {
        return Val().Equals(value);
    }

    public string Val() {
        return ((int)this.val).ToString();
    }
}

こんな風にenumをwrapするクラスを作りました。
そんで、使うときには、

// todayが火曜であることを確認する処理
string today = "2";//どこかから取得した値
bool b = Days.Tuesday.Equals(today);

// 日曜かどうかboolを取りたい場合
bool b = Days.Sunday.Equals(today);

毎回castするよりかは可読性は上がりました。


今回は、C#の場合でしたが、Javaでも同じような書き方が出来ます。
Javaではenumに独自メソッド書けるけど、まぁどっちがいいやろかね。

この記事みた後だと、今回の修正後もダサく感じてきます。

C#enum に関連する小技。

qiita.com

C#ではこのやり方が一番良いかも!

以上。

今日やったリファクタリング - 1 : 連番付きフィールド

あまりの糞コードに遭遇し、怒りのリファクタリングを行った。

ついでに「こんなリファクタリングしたよ」と残しておくとなんか良さそうだなと思い付き、
久しぶりにブログを書き始めた。

今日したのは、言語はC#で、
フィールドに連番の付いたフィールドがあり、そのどれかに値が入っていることを調べる処理のリファクタリング

モデルクラスは以下、

public class Model {
    // モデルのフィールドの構造は諸事情により、変更不可とする。
    // メソッドなどは追加してOK。
    public string Value1 { get; set; }
    public string Value2 { get; set; }
    public string Value3 { get; set; }
    public string Value4 { get; set; }
    public string Value5 { get; set; }
    public string Value6 { get; set; }
    public string Value7 { get; set; }
    public string Value8 { get; set; }
    public string Value9 { get; set; }
    public string Value10 { get; set; }
}

このモデルに対し、別の処理でこのどれかに値が入ってくる。 どのValueNに値が入っているか、Nの数値を取得したい。

Before

public class BusinessLogic {
    
    public int GetValueIndex(Model model) {
        if(! string.IsNullOrEmpty(model.Value10)) {
            return 10;
        }
        else if(! string.IsNullOrEmpty(model.Value9)) {
            return 9;
        }
        else if(! string.IsNullOrEmpty(model.Value8)) {
            return 8;
        }
        else if(! string.IsNullOrEmpty(model.Value7)) {
            return 7;
        }
        else if(! string.IsNullOrEmpty(model.Value6)) {
            return 6;
        }
        else if(! string.IsNullOrEmpty(model.Value5)) {
            return 5;
        }
        else if(! string.IsNullOrEmpty(model.Value4)) {
            return 4;
        }
        else if(! string.IsNullOrEmpty(model.Value3)) {
            return 3;
        }
        else if(! string.IsNullOrEmpty(model.Value2)) {
            return 2;
        }
        else if(! string.IsNullOrEmpty(model.Value1)) {
            return 1;
        }
        else
        {
            return 0;
        }
    }
}

あぁ!もう!!!

このIF文乱舞で嫌気が差さない場合は自分を疑ったほうが良い。

私が修正した結果がこちら

After

まず、ModelにValue達をDictionaryで返すメソッドを追加した。

public class Model {
    
    // モデルの構造は諸事情により、変更不可とする。
    public string Value1 { get; set; }
    public string Value2 { get; set; }
    public string Value3 { get; set; }
    public string Value4 { get; set; }
    public string Value5 { get; set; }
    public string Value6 { get; set; }
    public string Value7 { get; set; }
    public string Value8 { get; set; }
    public string Value9 { get; set; }
    public string Value10 { get; set; }

    public Dictionary<int, string> GetValues() {
        return new Dictionary<int, string>() {
            {1, Value1},
            {2, Value2},
            {3, Value3},
            {4, Value4},
            {5, Value5},
            {6, Value6},
            {7, Value7},
            {8, Value8},
            {9, Value9},
            {10, Value10},
        }
    }
}

このメソッドを利用して、BudinessLogicもこう修正した。

public class BusinessLogic {
    
    public int GetValueIndex(Model model) {
        return model.GetValues().Where(v => !string.IsNullOrEmpty(v)).Select(v => v.Key).Max();
    }
}

だいぶマシになった。

ついでにこの後、メソッド名もGetValueIndexでは意味が違っているので、GetMaxValueIndexに変更した。

リファクタリングはテストコードを書いて、現状のメソッド仕様を把握・テスト出来る状態にしてから取り掛かったほうが良いね。

以上。